Compare commits
No commits in common. "main" and "1.0.1" have entirely different histories.
23
.appveyor.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
environment:
|
||||
global:
|
||||
TOXENV: py
|
||||
|
||||
matrix:
|
||||
- PYTHON: C:\Python36
|
||||
- PYTHON: C:\Python27
|
||||
|
||||
init:
|
||||
- SET PATH=%PYTHON%;%PATH%
|
||||
|
||||
install:
|
||||
- python -m pip install -U pip setuptools wheel tox
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- python -m tox
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^.*-maintenance$/
|
||||
|
|
@ -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.rst merge=union
|
||||
33
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
**This issue tracker is a tool to address bugs in Flask itself.
|
||||
Please use the #pocoo IRC channel on freenode or Stack Overflow for general
|
||||
questions about using Flask or issues not related to Flask.**
|
||||
|
||||
If you'd like to report a bug in Flask, fill out the template below. Provide
|
||||
any any extra information that may be useful / related to your problem.
|
||||
Ideally, create an [MCVE](http://stackoverflow.com/help/mcve), which helps us
|
||||
understand the problem and helps check that it is not caused by something in
|
||||
your code.
|
||||
|
||||
---
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
Tell us what should happen.
|
||||
|
||||
```python
|
||||
Paste a minimal example that causes the problem.
|
||||
```
|
||||
|
||||
### Actual Behavior
|
||||
|
||||
Tell us what happens instead.
|
||||
|
||||
```pytb
|
||||
Paste the full traceback if there was an exception.
|
||||
```
|
||||
|
||||
### Environment
|
||||
|
||||
* Python version:
|
||||
* Flask version:
|
||||
* Werkzeug version:
|
||||
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?
|
||||
-->
|
||||
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Describe what this patch does to fix the issue.
|
||||
|
||||
Link to any relevant issues or pull requests.
|
||||
|
||||
<!--
|
||||
Commit checklist:
|
||||
|
||||
* add tests that fail without the patch
|
||||
* ensure all tests pass with ``pytest``
|
||||
* add documentation to the relevant docstrings or pages
|
||||
* add ``versionadded`` or ``versionchanged`` directives to relevant docstrings
|
||||
* add a changelog entry if this patch changes code
|
||||
|
||||
Tests, coverage, and docs will be run automatically when you submit the pull
|
||||
request, but running them yourself can save time.
|
||||
-->
|
||||
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
|
||||
25
.gitignore
vendored
|
|
@ -1,8 +1,23 @@
|
|||
.idea/
|
||||
.vscode/
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
.env
|
||||
.flaskenv
|
||||
*.pyc
|
||||
*.pyo
|
||||
env/
|
||||
env*
|
||||
dist/
|
||||
.coverage*
|
||||
htmlcov/
|
||||
build/
|
||||
*.egg
|
||||
*.egg-info/
|
||||
_mailinglist
|
||||
.tox/
|
||||
.cache/
|
||||
.pytest_cache/
|
||||
.idea/
|
||||
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 @@
|
|||
os: linux
|
||||
sudo: false
|
||||
language: python
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.6
|
||||
env: TOXENV=py,simplejson,devel,lowest,codecov
|
||||
- python: 3.6
|
||||
env: TOXENV=docs-html
|
||||
- python: 3.5
|
||||
env: TOXENV=py,codecov
|
||||
- python: 3.4
|
||||
env: TOXENV=py,codecov
|
||||
- python: 2.7
|
||||
env: TOXENV=py,simplejson,devel,lowest,codecov
|
||||
- python: pypy
|
||||
env: TOXENV=py,codecov
|
||||
- python: nightly
|
||||
env: TOXENV=py
|
||||
- os: osx
|
||||
language: generic
|
||||
env: TOXENV=py
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
env: TOXENV=py
|
||||
- os: osx
|
||||
language: generic
|
||||
env: TOXENV=py
|
||||
fast_finish: true
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
brew update;
|
||||
brew install python3 redis memcached;
|
||||
virtualenv -p python3 ~/py-env;
|
||||
. ~/py-env/bin/activate;
|
||||
fi
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
||||
script:
|
||||
- tox
|
||||
|
||||
cache:
|
||||
- pip
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^.*-maintenance$/
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
12
AUTHORS
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Flask is developed and maintained by the Pallets team and community
|
||||
contributors. It was created by Armin Ronacher. The core maintainers
|
||||
are:
|
||||
|
||||
- David Lord (davidism)
|
||||
- Adrian Mönnich (ThiefMaster)
|
||||
- Armin Ronacher (mitsuhiko)
|
||||
- Marcus Unterwaditzer (untitaker)
|
||||
|
||||
A full list of contributors is available from git with::
|
||||
|
||||
git shortlog -sne
|
||||
2147
CHANGES.rst
180
CONTRIBUTING.rst
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
How to contribute to Flask
|
||||
==========================
|
||||
|
||||
Thank you for considering contributing to Flask!
|
||||
|
||||
Support questions
|
||||
-----------------
|
||||
|
||||
Please, don't use the issue tracker for this. Use one of the following
|
||||
resources for questions about your own code:
|
||||
|
||||
* The IRC channel ``#pocoo`` on FreeNode.
|
||||
* The IRC channel ``#python`` on FreeNode for more general questions.
|
||||
* The mailing list flask@python.org for long term discussion or larger issues.
|
||||
* Ask on `Stack Overflow`_. Search with Google first using:
|
||||
``site:stackoverflow.com flask {search term, exception message, etc.}``
|
||||
|
||||
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?sort=linked
|
||||
|
||||
Reporting issues
|
||||
----------------
|
||||
|
||||
- Describe what you expected to happen.
|
||||
- If possible, include a `minimal, complete, and verifiable example`_ to help
|
||||
us identify the issue. This also helps check that the issue is not with your
|
||||
own code.
|
||||
- Describe what actually happened. Include the full traceback if there was an
|
||||
exception.
|
||||
- List your Python, Flask, and Werkzeug versions. If possible, check if this
|
||||
issue is already fixed in the repository.
|
||||
|
||||
.. _minimal, complete, and verifiable example: https://stackoverflow.com/help/mcve
|
||||
|
||||
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`_, but you may ignore the line length limit if following
|
||||
it would make the code uglier.
|
||||
|
||||
First time setup
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- Download and install the `latest version of git`_.
|
||||
- Configure git with your `username`_ and `email`_::
|
||||
|
||||
git config --global user.name 'your name'
|
||||
git config --global user.email 'your email'
|
||||
|
||||
- Make sure you have a `GitHub account`_.
|
||||
- Fork Flask to your GitHub account by clicking the `Fork`_ button.
|
||||
- `Clone`_ your GitHub fork locally::
|
||||
|
||||
git clone https://github.com/{username}/flask
|
||||
cd flask
|
||||
|
||||
- Add the main repository as a remote to update later::
|
||||
|
||||
git remote add pallets https://github.com/pallets/flask
|
||||
git fetch pallets
|
||||
|
||||
- Create a virtualenv::
|
||||
|
||||
python3 -m venv env
|
||||
. env/bin/activate
|
||||
# or "env\Scripts\activate" on Windows
|
||||
|
||||
- Install Flask in editable mode with development dependencies::
|
||||
|
||||
pip install -e ".[dev]"
|
||||
|
||||
.. _GitHub account: https://github.com/join
|
||||
.. _latest version of git: https://git-scm.com/downloads
|
||||
.. _username: https://help.github.com/articles/setting-your-username-in-git/
|
||||
.. _email: https://help.github.com/articles/setting-your-email-in-git/
|
||||
.. _Fork: https://github.com/pallets/flask/fork
|
||||
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
|
||||
|
||||
Start coding
|
||||
~~~~~~~~~~~~
|
||||
|
||||
- Create a branch to identify the issue you would like to work on (e.g.
|
||||
``2287-dry-test-suite``)
|
||||
- Using your favorite editor, make your changes, `committing as you go`_.
|
||||
- Try to follow `PEP8`_, but you may ignore the line length limit if following
|
||||
it would make the code uglier.
|
||||
- Include tests that cover any code changes you make. Make sure the test fails
|
||||
without your patch. `Run the tests. <contributing-testsuite_>`_.
|
||||
- Push your commits to GitHub and `create a pull request`_.
|
||||
- Celebrate 🎉
|
||||
|
||||
.. _committing as you go: http://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes
|
||||
.. _PEP8: https://pep8.org/
|
||||
.. _create a pull request: https://help.github.com/articles/creating-a-pull-request/
|
||||
|
||||
.. _contributing-testsuite:
|
||||
|
||||
Running the tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Run the basic test suite with::
|
||||
|
||||
pytest
|
||||
|
||||
This only runs the tests for the current environment. Whether this is relevant
|
||||
depends on which part of Flask you're working on. Travis-CI will run the full
|
||||
suite when you submit your pull request.
|
||||
|
||||
The full test suite takes a long time to run because it tests multiple
|
||||
combinations of Python and dependencies. You need to have Python 2.7, 3.4,
|
||||
3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run::
|
||||
|
||||
tox
|
||||
|
||||
Running test coverage
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Generating a report of lines that do not have test coverage can indicate
|
||||
where to start contributing. Run ``pytest`` using ``coverage`` and generate a
|
||||
report on the terminal and as an interactive HTML document::
|
||||
|
||||
coverage run -m pytest
|
||||
coverage report
|
||||
coverage html
|
||||
# then open htmlcov/index.html
|
||||
|
||||
Read more about `coverage <https://coverage.readthedocs.io>`_.
|
||||
|
||||
Running the full test suite with ``tox`` will combine the coverage reports
|
||||
from all runs.
|
||||
|
||||
|
||||
Building the docs
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Build the docs in the ``docs`` directory using Sphinx::
|
||||
|
||||
cd docs
|
||||
make html
|
||||
|
||||
Open ``_build/html/index.html`` in your browser to view the docs.
|
||||
|
||||
Read more about `Sphinx <http://www.sphinx-doc.org>`_.
|
||||
|
||||
|
||||
make targets
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Flask provides a ``Makefile`` with various shortcuts. They will ensure that
|
||||
all dependencies are installed.
|
||||
|
||||
- ``make test`` runs the basic test suite with ``pytest``
|
||||
- ``make cov`` runs the basic test suite with ``coverage``
|
||||
- ``make test-all`` runs the full test suite with ``tox``
|
||||
- ``make docs`` builds the HTML documentation
|
||||
|
||||
Caution: zero-padded file modes
|
||||
-------------------------------
|
||||
|
||||
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.
|
||||
|
||||
These files can also cause issues while cloning. 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.
|
||||
31
LICENSE
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
Copyright © 2010 by the Pallets team.
|
||||
|
||||
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.
|
||||
|
||||
* 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 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 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 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.rst LICENSE AUTHORS tox.ini
|
||||
|
||||
graft artwork
|
||||
graft tests
|
||||
graft examples
|
||||
graft docs
|
||||
|
||||
global-exclude *.py[co]
|
||||
|
||||
prune docs/_build
|
||||
prune docs/_themes
|
||||
35
Makefile
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
.PHONY: all install-dev test coverage cov test-all tox docs audit release clean-pyc upload-docs ebook
|
||||
|
||||
all: test
|
||||
|
||||
install-dev:
|
||||
pip install -q -e .[dev]
|
||||
|
||||
test: clean-pyc install-dev
|
||||
pytest
|
||||
|
||||
coverage: clean-pyc install-dev
|
||||
coverage run -m pytest
|
||||
coverage report
|
||||
coverage html
|
||||
|
||||
cov: coverage
|
||||
|
||||
test-all: install-dev
|
||||
tox
|
||||
|
||||
tox: test-all
|
||||
|
||||
docs: clean-pyc install-dev
|
||||
$(MAKE) -C docs html
|
||||
|
||||
audit:
|
||||
python setup.py audit
|
||||
|
||||
release:
|
||||
python scripts/make-release.py
|
||||
|
||||
clean-pyc:
|
||||
find . -name '*.pyc' -exec rm -f {} +
|
||||
find . -name '*.pyo' -exec rm -f {} +
|
||||
find . -name '*~' -exec rm -f {} +
|
||||
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/
|
||||
76
README.rst
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
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.
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Install and update using `pip`_:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
pip install -U Flask
|
||||
|
||||
|
||||
A Simple Example
|
||||
----------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
return 'Hello, World!'
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ FLASK_APP=hello.py flask run
|
||||
* Serving Flask app "hello"
|
||||
* 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://psfmember.org/civicrm/contribute/transact?reset=1&id=20
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
* Website: https://www.palletsprojects.com/p/flask/
|
||||
* Documentation: http://flask.pocoo.org/docs/
|
||||
* License: `BSD <https://github.com/pallets/flask/blob/master/LICENSE>`_
|
||||
* Releases: https://pypi.org/project/Flask/
|
||||
* Code: https://github.com/pallets/flask
|
||||
* Issue tracker: https://github.com/pallets/flask/issues
|
||||
* Test status:
|
||||
|
||||
* Linux, Mac: https://travis-ci.org/pallets/flask
|
||||
* Windows: https://ci.appveyor.com/project/pallets/flask
|
||||
|
||||
* Test coverage: https://codecov.io/gh/pallets/flask
|
||||
|
||||
.. _WSGI: https://wsgi.readthedocs.io
|
||||
.. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/
|
||||
.. _Jinja: https://www.palletsprojects.com/p/jinja/
|
||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
||||
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
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = Flask
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
|
|
@ -17,4 +17,4 @@ help:
|
|||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
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: 6.8 KiB |
BIN
docs/_static/logo-full.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/_static/no.png
vendored
Normal file
|
After Width: | Height: | Size: 259 B |
BIN
docs/_static/pycharm-run-config.png
vendored
|
Before Width: | Height: | Size: 97 KiB |
BIN
docs/_static/pycharm-runconfig.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/_static/touch-icon.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
docs/_static/yes.png
vendored
Normal file
|
After Width: | Height: | Size: 241 B |
47
docs/advanced_foreword.rst
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
.. _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.
|
||||
433
docs/api.rst
|
|
@ -1,9 +1,11 @@
|
|||
.. _api:
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. module:: flask
|
||||
|
||||
This part of the documentation covers all the interfaces of Flask. For
|
||||
This part of the documentation covers all the interfaces of Flask. For
|
||||
parts where Flask depends on external libraries, we document the most
|
||||
important right here and provide links to the canonical documentation.
|
||||
|
||||
|
|
@ -27,28 +29,76 @@ Incoming Request Data
|
|||
---------------------
|
||||
|
||||
.. autoclass:: Request
|
||||
:members:
|
||||
:inherited-members:
|
||||
:exclude-members: json_module
|
||||
:members:
|
||||
:inherited-members:
|
||||
|
||||
.. data:: request
|
||||
.. attribute:: environ
|
||||
|
||||
A proxy to the request data for the current request, an instance of
|
||||
:class:`.Request`.
|
||||
The underlying WSGI environment.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
.. attribute:: path
|
||||
.. attribute:: full_path
|
||||
.. attribute:: script_root
|
||||
.. attribute:: url
|
||||
.. attribute:: base_url
|
||||
.. attribute:: url_root
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
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:: 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
|
||||
----------------
|
||||
|
||||
.. autoclass:: flask.Response
|
||||
:members:
|
||||
:inherited-members:
|
||||
:exclude-members: json_module
|
||||
:members: set_cookie, max_cookie_size, data, mimetype, is_json, get_json
|
||||
|
||||
.. 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
|
||||
--------
|
||||
|
|
@ -60,33 +110,40 @@ does this is by using a signed cookie. The user can look at the session
|
|||
contents, but can't modify it unless they know the secret key, so make sure to
|
||||
set that to something complex and unguessable.
|
||||
|
||||
To access the current session you can use the :data:`.session` proxy.
|
||||
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
|
||||
:class:`.SessionMixin`.
|
||||
The session object works pretty much like an ordinary dict, with the
|
||||
difference that it keeps track on modifications.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
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
|
||||
keys. It cannot track modifications to mutable values, you need to set
|
||||
:attr:`~.SessionMixin.modified` manually when modifying a list, dict, etc.
|
||||
.. attribute:: new
|
||||
|
||||
.. code-block:: python
|
||||
``True`` if the session is new, ``False`` otherwise.
|
||||
|
||||
# appending to a list is not detected
|
||||
session["numbers"].append(42)
|
||||
.. attribute:: modified
|
||||
|
||||
``True`` if the session object detected a modification. Be advised
|
||||
that modifications on mutable structures are not picked up
|
||||
automatically, in that situation you have to explicitly set the
|
||||
attribute to ``True`` yourself. Here an example::
|
||||
|
||||
# this change is not picked up because a mutable object (here
|
||||
# a list) is changed.
|
||||
session['objects'].append(42)
|
||||
# so mark it as modified yourself
|
||||
session.modified = True
|
||||
|
||||
The session is persisted across requests using a cookie. By default the
|
||||
users's browser will clear the cookie when it is closed. Set
|
||||
:attr:`~.SessionMixin.permanent` to ``True`` to persist the cookie for
|
||||
:data:`PERMANENT_SESSION_LIFETIME`.
|
||||
.. attribute:: permanent
|
||||
|
||||
If set to ``True`` the session lives for
|
||||
:attr:`~flask.Flask.permanent_session_lifetime` seconds. The
|
||||
default is 31 days. If set to ``False`` (which is the default) the
|
||||
session will be deleted when the user closes the browser.
|
||||
|
||||
|
||||
Session Interface
|
||||
|
|
@ -116,9 +173,10 @@ implementation that Flask is using.
|
|||
|
||||
.. admonition:: Notice
|
||||
|
||||
The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``.
|
||||
The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a
|
||||
``timedelta``.
|
||||
The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer
|
||||
starting with Flask 0.8. Either catch this down yourself or use
|
||||
the :attr:`~flask.Flask.permanent_session_lifetime` attribute on the
|
||||
app which converts the result to an integer automatically.
|
||||
|
||||
|
||||
Test Client
|
||||
|
|
@ -146,24 +204,23 @@ Application Globals
|
|||
|
||||
To share data that is valid for one request only from one function to
|
||||
another, a global variable is not good enough because it would break in
|
||||
threaded environments. Flask provides you with a special object that
|
||||
threaded environments. Flask provides you with a special object that
|
||||
ensures it is only valid for the active request and that will return
|
||||
different values for each request. In a nutshell: it does the right
|
||||
thing, like it does for :data:`.request` and :data:`.session`.
|
||||
different values for each request. In a nutshell: it does the right
|
||||
thing, like it does for :class:`request` and :class:`session`.
|
||||
|
||||
.. data:: g
|
||||
|
||||
A proxy to a namespace object used to store data during a single request or
|
||||
app context. An instance of :attr:`.Flask.app_ctx_globals_class`, which
|
||||
defaults to :class:`._AppCtxGlobals`.
|
||||
A namespace object that can store data during an
|
||||
:doc:`application context </appcontext>`. This is an instance of
|
||||
:attr:`Flask.app_ctx_globals_class`, which defaults to
|
||||
:class:`ctx._AppCtxGlobals`.
|
||||
|
||||
This is a good place to store resources during a request. For example, a
|
||||
:meth:`~.Flask.before_request` function could load a user object from a
|
||||
session id, then set ``g.user`` to be used in the view function.
|
||||
This is a good place to store resources during a request. During
|
||||
testing, you can use the :ref:`faking-resources` pattern to
|
||||
pre-configure such resources.
|
||||
|
||||
This is only available when an :doc:`app context </appcontext>` is active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
.. versionchanged:: 0.10
|
||||
Bound to the application context instead of the request context.
|
||||
|
|
@ -177,16 +234,17 @@ Useful Functions and Classes
|
|||
|
||||
.. data:: current_app
|
||||
|
||||
A proxy to the :class:`.Flask` application handling the current request or
|
||||
other activity.
|
||||
A proxy to the application handling the current request. This is
|
||||
useful to access the application without needing to import it, or if
|
||||
it can't be imported, such as when using the application factory
|
||||
pattern or in blueprints and extensions.
|
||||
|
||||
This is useful to access the application without needing to import it, or if
|
||||
it can't be imported, such as when using the application factory pattern or
|
||||
in blueprints and extensions.
|
||||
This is only available when an
|
||||
:doc:`application context </appcontext>` is pushed. This happens
|
||||
automatically during requests and CLI commands. It can be controlled
|
||||
manually with :meth:`~flask.Flask.app_context`.
|
||||
|
||||
This is only available when an :doc:`app context </appcontext>` is active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
.. autofunction:: has_request_context
|
||||
|
||||
|
|
@ -208,6 +266,12 @@ Useful Functions and Classes
|
|||
|
||||
.. autofunction:: send_from_directory
|
||||
|
||||
.. autofunction:: safe_join
|
||||
|
||||
.. autofunction:: escape
|
||||
|
||||
.. autoclass:: Markup
|
||||
:members: escape, unescape, striptags
|
||||
|
||||
Message Flashing
|
||||
----------------
|
||||
|
|
@ -216,29 +280,58 @@ Message Flashing
|
|||
|
||||
.. autofunction:: get_flashed_messages
|
||||
|
||||
|
||||
JSON Support
|
||||
------------
|
||||
|
||||
.. module:: flask.json
|
||||
|
||||
Flask uses Python's built-in :mod:`json` module for handling JSON by
|
||||
default. The JSON implementation can be changed by assigning a different
|
||||
provider to :attr:`flask.Flask.json_provider_class` or
|
||||
:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will
|
||||
use methods on ``app.json`` if an app context is active.
|
||||
Flask uses ``simplejson`` for the JSON implementation. Since simplejson
|
||||
is provided by both the standard library as well as extension, Flask will
|
||||
try simplejson first and then fall back to the stdlib json module. On top
|
||||
of that it will delegate access to the current application's JSON encoders
|
||||
and decoders for easier customization.
|
||||
|
||||
Jinja's ``|tojson`` filter is configured to use the app's JSON provider.
|
||||
The filter marks the output with ``|safe``. Use it to render data inside
|
||||
HTML ``<script>`` tags.
|
||||
So for starters instead of doing::
|
||||
|
||||
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
|
||||
|
||||
<script>
|
||||
const names = {{ names|tojson }};
|
||||
renderChart(names, {{ axis_data|tojson }});
|
||||
<script type=text/javascript>
|
||||
doSomethingWith({{ user.username|tojson|safe }});
|
||||
</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:: dumps
|
||||
|
|
@ -249,17 +342,14 @@ HTML ``<script>`` tags.
|
|||
|
||||
.. autofunction:: load
|
||||
|
||||
.. autoclass:: flask.json.provider.JSONProvider
|
||||
:members:
|
||||
:member-order: bysource
|
||||
.. autoclass:: JSONEncoder
|
||||
:members:
|
||||
|
||||
.. autoclass:: flask.json.provider.DefaultJSONProvider
|
||||
:members:
|
||||
:member-order: bysource
|
||||
.. autoclass:: JSONDecoder
|
||||
:members:
|
||||
|
||||
.. automodule:: flask.json.tag
|
||||
|
||||
|
||||
Template Rendering
|
||||
------------------
|
||||
|
||||
|
|
@ -269,10 +359,6 @@ Template Rendering
|
|||
|
||||
.. autofunction:: render_template_string
|
||||
|
||||
.. autofunction:: stream_template
|
||||
|
||||
.. autofunction:: stream_template_string
|
||||
|
||||
.. autofunction:: get_template_attribute
|
||||
|
||||
Configuration
|
||||
|
|
@ -290,31 +376,59 @@ Stream Helpers
|
|||
Useful Internals
|
||||
----------------
|
||||
|
||||
.. autoclass:: flask.ctx.RequestContext
|
||||
:members:
|
||||
|
||||
.. data:: _request_ctx_stack
|
||||
|
||||
The internal :class:`~werkzeug.local.LocalStack` that holds
|
||||
:class:`~flask.ctx.RequestContext` instances. Typically, the
|
||||
:data:`request` and :data:`session` proxies should be accessed
|
||||
instead of the stack. It may be useful to access the stack in
|
||||
extension code.
|
||||
|
||||
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
|
||||
:members:
|
||||
|
||||
.. data:: flask.globals.app_ctx
|
||||
.. data:: _app_ctx_stack
|
||||
|
||||
A proxy to the active :class:`.AppContext`.
|
||||
The internal :class:`~werkzeug.local.LocalStack` that holds
|
||||
:class:`~flask.ctx.AppContext` instances. Typically, the
|
||||
:data:`current_app` and :data:`g` proxies should be accessed instead
|
||||
of the stack. Extensions can access the contexts on the stack as a
|
||||
namespace to store data.
|
||||
|
||||
This is an internal object that is essential to how Flask handles requests.
|
||||
Accessing this should not be needed in most cases. Most likely you want
|
||||
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` instead.
|
||||
|
||||
This is only available when a :doc:`request context </appcontext>` is
|
||||
active.
|
||||
|
||||
This is a proxy. See :ref:`context-visibility` for more information.
|
||||
|
||||
.. class:: flask.ctx.RequestContext
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Merged with :class:`AppContext`. This alias will be removed in Flask 4.0.
|
||||
|
||||
.. data:: flask.globals.request_ctx
|
||||
|
||||
.. deprecated:: 3.2
|
||||
Merged with :data:`.app_ctx`. This alias will be removed in Flask 4.0.
|
||||
.. versionadded:: 0.9
|
||||
|
||||
.. autoclass:: flask.blueprints.BlueprintSetupState
|
||||
:members:
|
||||
|
|
@ -324,13 +438,18 @@ Useful Internals
|
|||
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
|
||||
|
||||
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`
|
||||
and the context as dictionary (named `context`).
|
||||
|
||||
|
|
@ -364,7 +483,7 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
.. data:: request_started
|
||||
|
||||
This signal is sent when the request context is set up, before
|
||||
any request processing happens. Because the request context is already
|
||||
any request processing happens. Because the request context is already
|
||||
bound, the subscriber can access the request with the standard global
|
||||
proxies such as :class:`~flask.request`.
|
||||
|
||||
|
|
@ -384,7 +503,7 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
Example subscriber::
|
||||
|
||||
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)
|
||||
|
||||
from flask import request_finished
|
||||
|
|
@ -392,37 +511,23 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
|
||||
.. data:: got_request_exception
|
||||
|
||||
This signal is sent when an unhandled exception happens during
|
||||
request processing, including when debugging. The exception is
|
||||
passed to the subscriber as ``exception``.
|
||||
This signal is sent when an exception happens during request processing.
|
||||
It is sent *before* the standard exception handling kicks in and even
|
||||
in debug mode, where no exception handling happens. The exception
|
||||
itself is passed to the subscriber as `exception`.
|
||||
|
||||
This signal is not sent for
|
||||
:exc:`~werkzeug.exceptions.HTTPException`, or other exceptions that
|
||||
have error handlers registered, unless the exception was raised from
|
||||
an error handler.
|
||||
Example subscriber::
|
||||
|
||||
This example shows how to do some extra logging if a theoretical
|
||||
``SecurityException`` was raised:
|
||||
|
||||
.. code-block:: python
|
||||
def log_exception(sender, exception, **extra):
|
||||
sender.logger.debug('Got exception during processing: %s', exception)
|
||||
|
||||
from flask import got_request_exception
|
||||
|
||||
def log_security_exception(sender, exception, **extra):
|
||||
if not isinstance(exception, SecurityException):
|
||||
return
|
||||
|
||||
security_logger.exception(
|
||||
f"SecurityException at {request.url!r}",
|
||||
exc_info=exception,
|
||||
)
|
||||
|
||||
got_request_exception.connect(log_security_exception, app)
|
||||
got_request_exception.connect(log_exception, app)
|
||||
|
||||
.. data:: request_tearing_down
|
||||
|
||||
This signal is sent when the request is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
This signal is sent when the request is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
to this signal are called after the regular teardown handlers, but this
|
||||
is not something you can rely on.
|
||||
|
||||
|
|
@ -440,8 +545,8 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
|
||||
.. data:: appcontext_tearing_down
|
||||
|
||||
This signal is sent when the app context is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
This signal is sent when the app context is tearing down. This is always
|
||||
called, even if an exception is caused. Currently functions listening
|
||||
to this signal are called after the regular teardown handlers, but this
|
||||
is not something you can rely on.
|
||||
|
||||
|
|
@ -458,9 +563,9 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
|
||||
.. data:: appcontext_pushed
|
||||
|
||||
This signal is sent when an application context is pushed. The sender
|
||||
is the application. This is usually useful for unittests in order to
|
||||
temporarily hook in information. For instance it can be used to
|
||||
This signal is sent when an application context is pushed. The sender
|
||||
is the application. This is usually useful for unittests in order to
|
||||
temporarily hook in information. For instance it can be used to
|
||||
set a resource early onto the `g` object.
|
||||
|
||||
Example usage::
|
||||
|
|
@ -487,15 +592,16 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
|
||||
.. data:: appcontext_popped
|
||||
|
||||
This signal is sent when an application context is popped. The sender
|
||||
is the application. This usually falls in line with the
|
||||
This signal is sent when an application context is popped. The sender
|
||||
is the application. This usually falls in line with the
|
||||
:data:`appcontext_tearing_down` signal.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
|
||||
|
||||
.. data:: message_flashed
|
||||
|
||||
This signal is sent when the application is flashing a message. The
|
||||
This signal is sent when the application is flashing a message. The
|
||||
messages is sent as `message` keyword argument and the category as
|
||||
`category`.
|
||||
|
||||
|
|
@ -510,6 +616,24 @@ Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introd
|
|||
|
||||
.. 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 +661,7 @@ Generally there are three ways to define rules for the routing system:
|
|||
which is exposed as :attr:`flask.Flask.url_map`.
|
||||
|
||||
Variable parts in the route can be specified with angular brackets
|
||||
(``/user/<username>``). By default a variable part in the URL accepts any
|
||||
(``/user/<username>``). By default a variable part in the URL accepts any
|
||||
string without a slash however a different converter can be specified as
|
||||
well by using ``<converter:name>``.
|
||||
|
||||
|
|
@ -571,7 +695,7 @@ Here are some examples::
|
|||
pass
|
||||
|
||||
An important detail to keep in mind is how Flask deals with trailing
|
||||
slashes. The idea is to keep each URL unique so the following rules
|
||||
slashes. The idea is to keep each URL unique so the following rules
|
||||
apply:
|
||||
|
||||
1. If a rule ends with a slash and is requested without a slash by the
|
||||
|
|
@ -580,11 +704,11 @@ apply:
|
|||
2. If a rule does not end with a trailing slash and the user requests the
|
||||
page with a trailing slash, a 404 not found is raised.
|
||||
|
||||
This is consistent with how web servers deal with static files. This
|
||||
This is consistent with how web servers deal with static files. This
|
||||
also makes it possible to use relative link targets safely.
|
||||
|
||||
You can also define multiple rules for the same function. They have to be
|
||||
unique however. Defaults can also be specified. Here for example is a
|
||||
You can also define multiple rules for the same function. They have to be
|
||||
unique however. Defaults can also be specified. Here for example is a
|
||||
definition for a URL that accepts an optional page::
|
||||
|
||||
@app.route('/users/', defaults={'page': 1})
|
||||
|
|
@ -596,46 +720,47 @@ This specifies that ``/users/`` will be the URL for page one and
|
|||
``/users/page/N`` will be the URL for page ``N``.
|
||||
|
||||
If a URL contains a default value, it will be redirected to its simpler
|
||||
form with a 308 redirect. In the above example, ``/users/page/1`` will
|
||||
form with a 301 redirect. In the above example, ``/users/page/1`` will
|
||||
be redirected to ``/users/``. If your route handles ``GET`` and ``POST``
|
||||
requests, make sure the default route only handles ``GET``, as redirects
|
||||
can't preserve form data. ::
|
||||
|
||||
@app.route('/region/', defaults={'id': 1})
|
||||
@app.route('/region/<int:id>', methods=['GET', 'POST'])
|
||||
@app.route('/region/<id>', methods=['GET', 'POST'])
|
||||
def region(id):
|
||||
pass
|
||||
|
||||
Here are the parameters that :meth:`~flask.Flask.route` and
|
||||
:meth:`~flask.Flask.add_url_rule` accept. The only difference is that
|
||||
:meth:`~flask.Flask.add_url_rule` accept. The only difference is that
|
||||
with the route parameter the view function is defined with the decorator
|
||||
instead of the `view_func` parameter.
|
||||
|
||||
=============== ==========================================================
|
||||
`rule` the URL rule as string
|
||||
`endpoint` the endpoint for the registered URL rule. Flask itself
|
||||
`endpoint` the endpoint for the registered URL rule. Flask itself
|
||||
assumes that the name of the view function is the name
|
||||
of the endpoint if not explicitly stated.
|
||||
`view_func` the function to call when serving a request to the
|
||||
provided endpoint. If this is not provided one can
|
||||
provided endpoint. If this is not provided one can
|
||||
specify the function later by storing it in the
|
||||
:attr:`~flask.Flask.view_functions` dictionary with the
|
||||
endpoint as key.
|
||||
`defaults` A dictionary with defaults for this rule. See the
|
||||
`defaults` A dictionary with defaults for this rule. See the
|
||||
example above for how defaults work.
|
||||
`subdomain` specifies the rule for the subdomain in case subdomain
|
||||
matching is in use. If not specified the default
|
||||
matching is in use. If not specified the default
|
||||
subdomain is assumed.
|
||||
`**options` the options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object. A change to
|
||||
Werkzeug is handling of method options. methods is a list
|
||||
:class:`~werkzeug.routing.Rule` object. A change to
|
||||
Werkzeug is handling of method options. methods is a list
|
||||
of methods this rule should be limited to (``GET``, ``POST``
|
||||
etc.). By default a rule just listens for ``GET`` (and
|
||||
implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is
|
||||
etc.). By default a rule just listens for ``GET`` (and
|
||||
implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is
|
||||
implicitly added and handled by the standard request
|
||||
handling. They have to be specified as keyword arguments.
|
||||
handling. They have to be specified as keyword arguments.
|
||||
=============== ==========================================================
|
||||
|
||||
.. _view-func-options:
|
||||
|
||||
View Function Options
|
||||
---------------------
|
||||
|
|
@ -645,19 +770,19 @@ customize behavior the view function would normally not have control over.
|
|||
The following attributes can be provided optionally to either override
|
||||
some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior:
|
||||
|
||||
- `__name__`: The name of a function is by default used as endpoint. If
|
||||
endpoint is provided explicitly this value is used. Additionally this
|
||||
- `__name__`: The name of a function is by default used as endpoint. If
|
||||
endpoint is provided explicitly this value is used. Additionally this
|
||||
will be prefixed with the name of the blueprint by default which
|
||||
cannot be customized from the function itself.
|
||||
|
||||
- `methods`: If methods are not provided when the URL rule is added,
|
||||
Flask will look on the view function object itself if a `methods`
|
||||
attribute exists. If it does, it will pull the information for the
|
||||
attribute exists. If it does, it will pull the information for the
|
||||
methods from there.
|
||||
|
||||
- `provide_automatic_options`: if this attribute is set Flask will
|
||||
either force enable or disable the automatic implementation of the
|
||||
HTTP ``OPTIONS`` response. This can be useful when working with
|
||||
HTTP ``OPTIONS`` response. This can be useful when working with
|
||||
decorators that want to customize the ``OPTIONS`` response on a per-view
|
||||
basis.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,63 +1,76 @@
|
|||
The App and Request Context
|
||||
===========================
|
||||
.. currentmodule:: flask
|
||||
|
||||
The context keeps track of data and objects during a request, CLI command, or
|
||||
other activity. Rather than passing this data around to every function, the
|
||||
:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` proxies
|
||||
are accessed instead.
|
||||
.. _app-context:
|
||||
|
||||
When handling a request, the context is referred to as the "request context"
|
||||
because it contains request data in addition to application data. Otherwise,
|
||||
such as during a CLI command, it is referred to as the "app context". During an
|
||||
app context, :data:`.current_app` and :data:`.g` are available, while during a
|
||||
request context :data:`.request` and :data:`.session` are also available.
|
||||
The Application Context
|
||||
=======================
|
||||
|
||||
The application context keeps track of the application-level data during
|
||||
a request, CLI command, or other activity. Rather than passing the
|
||||
application around to each function, the :data:`current_app` and
|
||||
:data:`g` proxies are accessed instead.
|
||||
|
||||
This is similar to the :doc:`/reqcontext`, which keeps track of
|
||||
request-level data during a request. A corresponding application context
|
||||
is pushed when a request context is pushed.
|
||||
|
||||
Purpose of the Context
|
||||
----------------------
|
||||
|
||||
The context and proxies help solve two development issues: circular imports, and
|
||||
passing around global data during a request.
|
||||
The :class:`Flask` application object has attributes, such as
|
||||
:attr:`~Flask.config`, that are useful to access within views and
|
||||
:doc:`CLI commands </cli>`. However, importing the ``app`` instance
|
||||
within the modules in your project is prone to circular import issues.
|
||||
When using the :doc:`app factory pattern </patterns/appfactories>` or
|
||||
writing reusable :doc:`blueprints </blueprints>` or
|
||||
:doc:`extensions </extensions>` there won't be an ``app`` instance to
|
||||
import at all.
|
||||
|
||||
The :class:`.Flask` application object has attributes, such as
|
||||
:attr:`~.Flask.config`, that are useful to access within views and other
|
||||
functions. However, importing the ``app`` instance within the modules in your
|
||||
project is prone to circular import issues. When using the
|
||||
:doc:`app factory pattern </patterns/appfactories>` or writing reusable
|
||||
:doc:`blueprints </blueprints>` or :doc:`extensions </extensions>` there won't
|
||||
be an ``app`` instance to import at all.
|
||||
Flask solves this issue with the *application context*. Rather than
|
||||
referring to an ``app`` directly, you use the the :data:`current_app`
|
||||
proxy, which points to the application handling the current activity.
|
||||
|
||||
When the application handles a request, it creates a :class:`.Request` object.
|
||||
Because a *worker* handles only one request at a time, the request data can be
|
||||
considered global to that worker during that request. Passing it as an argument
|
||||
through every function during the request becomes verbose and redundant.
|
||||
Flask automatically *pushes* an application context when handling a
|
||||
request. View functions, error handlers, and other functions that run
|
||||
during a request will have access to :data:`current_app`.
|
||||
|
||||
Flask solves these issues with the *active context* pattern. Rather than
|
||||
importing an ``app`` directly, or having to pass it and the request through to
|
||||
every single function, you import and access the proxies, which point to the
|
||||
currently active application and request data. This is sometimes referred to
|
||||
as "context local" data.
|
||||
Flask will also automatically push an app context when running CLI
|
||||
commands registered with :attr:`Flask.cli` using ``@app.cli.command()``.
|
||||
|
||||
|
||||
Context During Setup
|
||||
--------------------
|
||||
Lifetime of the Context
|
||||
-----------------------
|
||||
|
||||
If you try to access :data:`.current_app`, :data:`.g`, or anything that uses it,
|
||||
outside an app context, you'll get this error message:
|
||||
The application context is created and destroyed as necessary. When a
|
||||
Flask application begins handling a request, it pushes an application
|
||||
context and a :doc:`request context </reqcontext>`. When the request
|
||||
ends it pops the request context then the application context.
|
||||
Typically, an application context will have the same lifetime as a
|
||||
request.
|
||||
|
||||
See :doc:`/reqcontext` for more information about how the contexts work
|
||||
and the full lifecycle of a request.
|
||||
|
||||
|
||||
Manually Push a Context
|
||||
-----------------------
|
||||
|
||||
If you try to access :data:`current_app`, or anything that uses it,
|
||||
outside an application context, you'll get this error message:
|
||||
|
||||
.. code-block:: pytb
|
||||
|
||||
RuntimeError: Working outside of application context.
|
||||
|
||||
Attempted to use functionality that expected a current application to be
|
||||
set. To solve this, set up an app context using 'with app.app_context()'.
|
||||
See the documentation on app context for more information.
|
||||
This typically means that you attempted to use functionality that
|
||||
needed to interface with the current application object in some way.
|
||||
To solve this, set up an application context with app.app_context().
|
||||
|
||||
If you see that error while configuring your application, such as when
|
||||
initializing an extension, you can push a context manually since you have direct
|
||||
access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block.
|
||||
|
||||
.. code-block:: python
|
||||
initializing an extension, you can push a context manually since you
|
||||
have direct access to the ``app``. Use :meth:`~Flask.app_context` in a
|
||||
``with`` block, and everything that runs in the block will have access
|
||||
to :data:`current_app`. ::
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
|
|
@ -67,121 +80,80 @@ access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block.
|
|||
|
||||
return app
|
||||
|
||||
If you see that error somewhere else in your code not related to setting up the
|
||||
application, it most likely indicates that you should move that code into a view
|
||||
function or CLI command.
|
||||
If you see that error somewhere else in your code not related to
|
||||
configuring the application, it most likely indicates that you should
|
||||
move that code into a view function or CLI command.
|
||||
|
||||
|
||||
Context During Testing
|
||||
----------------------
|
||||
Storing Data
|
||||
------------
|
||||
|
||||
See :doc:`/testing` for detailed information about managing the context during
|
||||
tests.
|
||||
The application context is a good place to store common data during a
|
||||
request or CLI command. Flask provides the :data:`g object <g>` for this
|
||||
purpose. It is a simple namespace object that has the same lifetime as
|
||||
an application context.
|
||||
|
||||
If you try to access :data:`.request`, :data:`.session`, or anything that uses
|
||||
it, outside a request context, you'll get this error message:
|
||||
.. note::
|
||||
The ``g`` name stands for "global", but that is referring to the
|
||||
data being global *within a context*. The data on ``g`` is lost
|
||||
after the context ends, and it is not an appropriate place to store
|
||||
data between requests. Use the :data:`session` or a database to
|
||||
store data across requests.
|
||||
|
||||
.. code-block:: pytb
|
||||
A common use for :data:`g` is to manage resources during a request.
|
||||
|
||||
RuntimeError: Working outside of request context.
|
||||
1. ``get_X()`` creates resource ``X`` if it does not exist, caching it
|
||||
as ``g.X``.
|
||||
2. ``teardown_X()`` closes or otherwise deallocates the resource if it
|
||||
exists. It is registered as a :meth:`~Flask.teardown_appcontext`
|
||||
handler.
|
||||
|
||||
Attempted to use functionality that expected an active HTTP request. See the
|
||||
documentation on request context for more information.
|
||||
For example, you can manage a database connection using this pattern::
|
||||
|
||||
This will probably only happen during tests. If you see that error somewhere
|
||||
else in your code not related to testing, it most likely indicates that you
|
||||
should move that code into a view function.
|
||||
from flask import g
|
||||
|
||||
The primary way to solve this is to use :meth:`.Flask.test_client` to simulate
|
||||
a full request.
|
||||
def get_db():
|
||||
if 'db' not in g:
|
||||
g.db = connect_to_database()
|
||||
|
||||
If you only want to unit test one function, rather than a full request, use
|
||||
:meth:`.Flask.test_request_context` in a ``with`` block.
|
||||
return g.db
|
||||
|
||||
.. code-block:: python
|
||||
@app.teardown_appcontext
|
||||
def teardown_db():
|
||||
db = g.pop('db', None)
|
||||
|
||||
def generate_report(year):
|
||||
format = request.args.get("format")
|
||||
...
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
with app.test_request_context(
|
||||
"/make_report/2017", query_string={"format": "short"}
|
||||
):
|
||||
generate_report()
|
||||
During a request, every call to ``get_db()`` will return the same
|
||||
connection, and it will be closed automatically at the end of the
|
||||
request.
|
||||
|
||||
You can use :class:`~werkzeug.local.LocalProxy` to make a new context
|
||||
local from ``get_db()``::
|
||||
|
||||
from werkzeug.local import LocalProxy
|
||||
db = LocalProxy(get_db)
|
||||
|
||||
Accessing ``db`` will call ``get_db`` internally, in the same way that
|
||||
:data:`current_app` works.
|
||||
|
||||
----
|
||||
|
||||
If you're writing an extension, :data:`g` should be reserved for user
|
||||
code. You may store internal data on the context itself, but be sure to
|
||||
use a sufficiently unique name. The current context is accessed with
|
||||
:data:`_app_ctx_stack.top <_app_ctx_stack>`. For more information see
|
||||
:doc:`extensiondev`.
|
||||
|
||||
|
||||
.. _context-visibility:
|
||||
Events and Signals
|
||||
------------------
|
||||
|
||||
Visibility of the Context
|
||||
-------------------------
|
||||
The application will call functions registered with
|
||||
:meth:`~Flask.teardown_appcontext` when the application context is
|
||||
popped.
|
||||
|
||||
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.
|
||||
If :data:`~signals.signals_available` is true, the following signals are
|
||||
sent: :data:`appcontext_pushed`, :data:`appcontext_tearing_down`, and
|
||||
:data:`appcontext_popped`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
====================================
|
||||
|
||||
|
|
@ -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
|
||||
application -- it's a set of operations which can be registered on an
|
||||
application, even multiple times. Why not have multiple application
|
||||
objects? You can do that (see :doc:`/patterns/appdispatch`), but your
|
||||
applications will have separate configs and will be managed at the WSGI
|
||||
layer.
|
||||
objects? You can do that (see :ref:`app-dispatch`), but your applications
|
||||
will have separate configs and will be managed at the WSGI layer.
|
||||
|
||||
Blueprints instead provide separation at the Flask level, share
|
||||
application config, and can change an application object as necessary with
|
||||
|
|
@ -69,17 +70,16 @@ implement a blueprint that does simple rendering of static templates::
|
|||
@simple_page.route('/<page>')
|
||||
def show(page):
|
||||
try:
|
||||
return render_template(f'pages/{page}.html')
|
||||
return render_template('pages/%s.html' % page)
|
||||
except TemplateNotFound:
|
||||
abort(404)
|
||||
|
||||
When you bind a function with the help of the ``@simple_page.route``
|
||||
decorator, the blueprint will record the intention of registering the
|
||||
function ``show`` on the application when it's later registered.
|
||||
decorator the blueprint will record the intention of registering the
|
||||
function `show` on the application when it's later registered.
|
||||
Additionally it will prefix the endpoint of the function with the
|
||||
name of the blueprint which was given to the :class:`Blueprint`
|
||||
constructor (in this case also ``simple_page``). The blueprint's name
|
||||
does not modify the URL, only the endpoint.
|
||||
constructor (in this case also ``simple_page``).
|
||||
|
||||
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
|
||||
these::
|
||||
|
||||
>>> app.url_map
|
||||
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||
<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
|
||||
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::
|
||||
|
||||
>>> app.url_map
|
||||
Map([<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||
[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
|
||||
<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
|
||||
blueprint might respond properly to that. In fact it depends on how the
|
||||
blueprint is implemented if it can be mounted more than once.
|
||||
|
||||
Nesting Blueprints
|
||||
------------------
|
||||
|
||||
It is possible to register a blueprint on another blueprint.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parent = Blueprint('parent', __name__, url_prefix='/parent')
|
||||
child = Blueprint('child', __name__, url_prefix='/child')
|
||||
parent.register_blueprint(child)
|
||||
app.register_blueprint(parent)
|
||||
|
||||
The child blueprint will gain the parent's name as a prefix to its
|
||||
name, and child URLs will be prefixed with the parent's URL prefix.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
url_for('parent.child.create')
|
||||
/parent/child/create
|
||||
|
||||
In addition a child blueprint's will gain their parent's subdomain,
|
||||
with their subdomain as prefix if present i.e.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
parent = Blueprint('parent', __name__, subdomain='parent')
|
||||
child = Blueprint('child', __name__, subdomain='child')
|
||||
parent.register_blueprint(child)
|
||||
app.register_blueprint(parent)
|
||||
|
||||
url_for('parent.child.create', _external=True)
|
||||
"child.parent.domain.tld"
|
||||
|
||||
Blueprint-specific before request functions, etc. registered with the
|
||||
parent will trigger for the child. If a child does not have an error
|
||||
handler that can handle a given exception, the parent's will be tried.
|
||||
|
||||
|
||||
Blueprint Resources
|
||||
-------------------
|
||||
|
||||
|
|
@ -280,11 +240,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
|
||||
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
|
||||
pages.
|
||||
|
||||
|
|
@ -299,7 +258,7 @@ 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
|
||||
the application instance has no way of knowing which blueprint errorhandler 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::
|
||||
|
|
@ -308,8 +267,8 @@ at the application level using the ``request`` proxy object::
|
|||
@app.errorhandler(405)
|
||||
def _handle_api_error(ex):
|
||||
if request.path.startswith('/api/'):
|
||||
return jsonify(error=str(ex)), ex.code
|
||||
return jsonify_error(ex)
|
||||
else:
|
||||
return ex
|
||||
|
||||
See :doc:`/errorhandling`.
|
||||
More information on error handling see :ref:`errorpages`.
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
Changes
|
||||
=======
|
||||
|
||||
.. include:: ../CHANGES.rst
|
||||
464
docs/cli.rst
|
|
@ -1,5 +1,7 @@
|
|||
.. currentmodule:: flask
|
||||
|
||||
.. _cli:
|
||||
|
||||
Command Line Interface
|
||||
======================
|
||||
|
||||
|
|
@ -8,61 +10,78 @@ 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/
|
||||
.. _Click: http://click.pocoo.org/
|
||||
|
||||
|
||||
Application Discovery
|
||||
---------------------
|
||||
|
||||
The ``flask`` command is installed by Flask, not your application; it must be
|
||||
told where to find your application in order to use it. The ``--app``
|
||||
option is used to specify how to load the application.
|
||||
told where to find your application in order to use it. The ``FLASK_APP``
|
||||
environment variable is used to specify how to load the application.
|
||||
|
||||
While ``--app`` supports a variety of options for specifying your
|
||||
Unix Bash (Linux, Mac, etc.)::
|
||||
|
||||
$ export FLASK_APP=hello
|
||||
$ flask run
|
||||
|
||||
Windows CMD::
|
||||
|
||||
> set FLASK_APP=hello
|
||||
> flask run
|
||||
|
||||
Windows PowerShell::
|
||||
|
||||
> $env:FLASK_APP = "hello"
|
||||
> flask run
|
||||
|
||||
While ``FLASK_APP`` supports a variety of options for specifying your
|
||||
application, most use cases should be simple. Here are the typical values:
|
||||
|
||||
(nothing)
|
||||
The name "app" or "wsgi" is imported (as a ".py" file, or package),
|
||||
automatically detecting an app (``app`` or ``application``) or
|
||||
factory (``create_app`` or ``make_app``).
|
||||
The file :file:`wsgi.py` is imported, automatically detecting an app
|
||||
(``app``). This provides an easy way to create an app from a factory with
|
||||
extra arguments.
|
||||
|
||||
``--app hello``
|
||||
The given name is imported, automatically detecting an app (``app``
|
||||
or ``application``) or factory (``create_app`` or ``make_app``).
|
||||
``FLASK_APP=hello``
|
||||
The name is imported, automatically detecting an app (``app``) or factory
|
||||
(``create_app``).
|
||||
|
||||
----
|
||||
|
||||
``--app`` has three parts: an optional path that sets the current working
|
||||
``FLASK_APP`` has three parts: an optional path that sets the current working
|
||||
directory, a Python file or dotted import path, and an optional variable
|
||||
name of the instance or factory. If the name is a factory, it can optionally
|
||||
be followed by arguments in parentheses. The following values demonstrate these
|
||||
parts:
|
||||
|
||||
``--app src/hello``
|
||||
``FLASK_APP=src/hello``
|
||||
Sets the current working directory to ``src`` then imports ``hello``.
|
||||
|
||||
``--app hello.web``
|
||||
``FLASK_APP=hello.web``
|
||||
Imports the path ``hello.web``.
|
||||
|
||||
``--app hello:app2``
|
||||
``FLASK_APP=hello:app2``
|
||||
Uses the ``app2`` Flask instance in ``hello``.
|
||||
|
||||
``--app 'hello:create_app("dev")'``
|
||||
``FLASK_APP="hello:create_app('dev')"``
|
||||
The ``create_app`` factory in ``hello`` is called with the string ``'dev'``
|
||||
as the argument.
|
||||
|
||||
If ``--app`` is not set, the command will try to import "app" or
|
||||
"wsgi" (as a ".py" file, or package) and try to detect an application
|
||||
instance or factory.
|
||||
If ``FLASK_APP`` is not set, the command will look for a file called
|
||||
:file:`wsgi.py` or :file:`app.py` and try to detect an application instance or
|
||||
factory.
|
||||
|
||||
Within the given import, the command looks for an application instance named
|
||||
``app`` or ``application``, then any application instance. If no instance is
|
||||
found, the command looks for a factory function named ``create_app`` or
|
||||
``make_app`` that returns an instance.
|
||||
|
||||
If parentheses follow the factory name, their contents are parsed as
|
||||
Python literals and passed as arguments and keyword arguments to the
|
||||
function. This means that strings must still be in quotes.
|
||||
When calling an application factory, if the factory takes an argument named
|
||||
``info``, then the :class:`~cli.ScriptInfo` instance is passed as a keyword
|
||||
argument. If parentheses follow the factory name, their contents are parsed
|
||||
as Python literals and passes as arguments to the function. This means that
|
||||
strings must still be in quotes.
|
||||
|
||||
|
||||
Run the Development Server
|
||||
|
|
@ -71,63 +90,14 @@ Run the Development Server
|
|||
The :func:`run <cli.run_command>` command will start the development server. It
|
||||
replaces the :meth:`Flask.run` method in most cases. ::
|
||||
|
||||
$ flask --app hello run
|
||||
$ flask run
|
||||
* Serving Flask app "hello"
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
|
||||
.. warning:: Do not use this command to run your application in production.
|
||||
Only use the development server during development. The development server
|
||||
is provided for convenience, but is not designed to be particularly secure,
|
||||
stable, or efficient. See :doc:`/deploying/index` for how to run in production.
|
||||
|
||||
If another program is already using port 5000, you'll see
|
||||
``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the
|
||||
server tries to start. See :ref:`address-already-in-use` for how to
|
||||
handle that.
|
||||
|
||||
|
||||
Debug Mode
|
||||
~~~~~~~~~~
|
||||
|
||||
In debug mode, the ``flask run`` command will enable the interactive debugger and the
|
||||
reloader by default, and make errors easier to see and debug. To enable debug mode, use
|
||||
the ``--debug`` option.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ flask --app hello run --debug
|
||||
* Serving Flask app "hello"
|
||||
* Debug mode: on
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
* Restarting with inotify reloader
|
||||
* Debugger is active!
|
||||
* Debugger PIN: 223-456-919
|
||||
|
||||
The ``--debug`` option can also be passed to the top level ``flask`` command to enable
|
||||
debug mode for any command. The following two ``run`` calls are equivalent.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ flask --app hello --debug run
|
||||
$ flask --app hello run --debug
|
||||
|
||||
|
||||
Watch and Ignore Files with the Reloader
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When using debug mode, the reloader will trigger whenever your Python code or imported
|
||||
modules change. The reloader can watch additional files with the ``--extra-files``
|
||||
option. Multiple paths are separated with ``:``, or ``;`` on Windows.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask run --extra-files file1:dirA/file2:dirB/
|
||||
* Running on http://127.0.0.1:8000/
|
||||
* Detected change in '/path/to/file1', reloading
|
||||
|
||||
The reloader can also ignore files using :mod:`fnmatch` patterns with the
|
||||
``--exclude-patterns`` option. Multiple patterns are separated with ``:``, or ``;`` on
|
||||
Windows.
|
||||
stable, or efficient. See :ref:`deployment` for how to run in production.
|
||||
|
||||
|
||||
Open a Shell
|
||||
|
|
@ -138,34 +108,63 @@ 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
|
||||
Python 3.6.2 (default, Jul 20 2017, 03:52:27)
|
||||
[GCC 7.1.1 20170630] on linux
|
||||
App: example
|
||||
Instance: /home/user/Projects/hello/instance
|
||||
>>>
|
||||
|
||||
Use :meth:`~Flask.shell_context_processor` to add other automatic imports.
|
||||
|
||||
|
||||
Environments
|
||||
------------
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
The environment in which the Flask app runs is set by the
|
||||
:envvar:`FLASK_ENV` environment variable. If not set it defaults to
|
||||
``production``. The other recognized environment is ``development``.
|
||||
Flask and extensions may choose to enable behaviors based on the
|
||||
environment.
|
||||
|
||||
If the env is set to ``development``, the ``flask`` command will enable
|
||||
debug mode and ``flask run`` will enable the interactive debugger and
|
||||
reloader.
|
||||
|
||||
::
|
||||
|
||||
$ FLASK_ENV=development flask run
|
||||
* Serving Flask app "hello"
|
||||
* Environment: development
|
||||
* 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
|
||||
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
|
||||
Debug mode will be enabled when :envvar:`FLASK_ENV` is ``development``,
|
||||
as described above. If you want to control debug mode separately, use
|
||||
:envvar:`FLASK_DEBUG`. The value ``1`` enables it, ``0`` disables it.
|
||||
|
||||
|
||||
.. _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.
|
||||
Rather than setting ``FLASK_APP`` each 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.
|
||||
environment variables defined in the files :file:`.env` and :file:`.flaskenv`.
|
||||
This can be used to avoid having to set ``FLASK_APP`` manually every time you
|
||||
open a new terminal, 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
|
||||
|
|
@ -173,7 +172,9 @@ 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.
|
||||
from to locate the files. The current working directory will be set to the
|
||||
location of the file, with the assumption that that is the top level project
|
||||
directory.
|
||||
|
||||
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
|
||||
|
|
@ -190,39 +191,11 @@ 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::
|
||||
.. code-block:: none
|
||||
|
||||
.. 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/
|
||||
export 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.
|
||||
|
|
@ -234,9 +207,9 @@ Disable dotenv
|
|||
The ``flask`` command will show a message if it detects dotenv files but
|
||||
python-dotenv is not installed.
|
||||
|
||||
.. code-block:: bash
|
||||
.. code-block:: none
|
||||
|
||||
$ flask run
|
||||
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
|
||||
|
|
@ -246,35 +219,10 @@ 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::
|
||||
.. code-block:: none
|
||||
|
||||
.. 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
|
||||
export FLASK_SKIP_DOTENV=1
|
||||
flask run
|
||||
|
||||
|
||||
Environment Variables From virtualenv
|
||||
|
|
@ -284,31 +232,13 @@ 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::
|
||||
Unix Bash, :file:`venv/bin/activate`::
|
||||
|
||||
.. group-tab:: Bash
|
||||
export FLASK_APP=hello
|
||||
|
||||
Unix Bash, :file:`.venv/bin/activate`::
|
||||
Windows CMD, :file:`venv\\Scripts\\activate.bat`::
|
||||
|
||||
$ 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"
|
||||
set 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
|
||||
|
|
@ -321,7 +251,7 @@ Custom Commands
|
|||
The ``flask`` command is implemented using `Click`_. See that project's
|
||||
documentation for full information about writing commands.
|
||||
|
||||
This example adds the command ``create-user`` that takes the argument
|
||||
This example adds the command ``create_user`` that takes the argument
|
||||
``name``. ::
|
||||
|
||||
import click
|
||||
|
|
@ -329,14 +259,14 @@ This example adds the command ``create-user`` that takes the argument
|
|||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.cli.command("create-user")
|
||||
@click.argument("name")
|
||||
@app.cli.command()
|
||||
@click.argument('name')
|
||||
def create_user(name):
|
||||
...
|
||||
|
||||
::
|
||||
|
||||
$ flask create-user admin
|
||||
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. ::
|
||||
|
|
@ -357,105 +287,62 @@ group. This is useful if you want to organize multiple related commands. ::
|
|||
|
||||
::
|
||||
|
||||
$ flask user create demo
|
||||
flask user create demo
|
||||
|
||||
See :ref:`testing-cli` for an overview of how to test your custom
|
||||
commands.
|
||||
|
||||
|
||||
Registering Commands with Blueprints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If your application uses blueprints, you can optionally register CLI
|
||||
commands directly onto them. When your blueprint is registered onto your
|
||||
application, the associated commands will be available to the ``flask``
|
||||
command. By default, those commands will be nested in a group matching
|
||||
the name of the blueprint.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('students', __name__)
|
||||
|
||||
@bp.cli.command('create')
|
||||
@click.argument('name')
|
||||
def create(name):
|
||||
...
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask students create alice
|
||||
|
||||
You can alter the group name by specifying the ``cli_group`` parameter
|
||||
when creating the :class:`Blueprint` object, or later with
|
||||
:meth:`app.register_blueprint(bp, cli_group='...') <Flask.register_blueprint>`.
|
||||
The following are equivalent:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('students', __name__, cli_group='other')
|
||||
# or
|
||||
app.register_blueprint(bp, cli_group='other')
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask other create alice
|
||||
|
||||
Specifying ``cli_group=None`` will remove the nesting and merge the
|
||||
commands directly to the application's level:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bp = Blueprint('students', __name__, cli_group=None)
|
||||
# or
|
||||
app.register_blueprint(bp, cli_group=None)
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask create alice
|
||||
|
||||
|
||||
Application Context
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Commands added using the Flask app's :attr:`~Flask.cli` or
|
||||
:class:`~flask.cli.FlaskGroup` :meth:`~cli.AppGroup.command` decorator
|
||||
will be executed with an application context pushed, so your custom
|
||||
commands and parameters have access to the app and its configuration. The
|
||||
:func:`~cli.with_appcontext` decorator can be used to get the same
|
||||
behavior, but is not needed in most cases.
|
||||
|
||||
.. code-block:: python
|
||||
Commands added using the Flask app's :attr:`~Flask.cli`
|
||||
:meth:`~cli.AppGroup.command` decorator will be executed with an application
|
||||
context pushed, so your command and extensions have access to the app and its
|
||||
configuration. If you create a command using the Click :func:`~click.command`
|
||||
decorator instead of the Flask decorator, you can use
|
||||
:func:`~cli.with_appcontext` to get the same behavior. ::
|
||||
|
||||
import click
|
||||
from flask.cli import with_appcontext
|
||||
|
||||
@click.command()
|
||||
@click.command
|
||||
@with_appcontext
|
||||
def do_work():
|
||||
...
|
||||
|
||||
app.cli.add_command(do_work)
|
||||
|
||||
If you're sure a command doesn't need the context, you can disable it::
|
||||
|
||||
@app.cli.command(with_appcontext=False)
|
||||
def do_work():
|
||||
...
|
||||
|
||||
|
||||
Plugins
|
||||
-------
|
||||
|
||||
Flask will automatically load commands specified in the ``flask.commands``
|
||||
`entry point`_. This is useful for extensions that want to add commands when
|
||||
they are installed. Entry points are specified in :file:`pyproject.toml`:
|
||||
they are installed. Entry points are specified in :file:`setup.py` ::
|
||||
|
||||
.. code-block:: toml
|
||||
from setuptools import setup
|
||||
|
||||
[project.entry-points."flask.commands"]
|
||||
my-command = "my_extension.commands:cli"
|
||||
setup(
|
||||
name='flask-my-extension',
|
||||
...,
|
||||
entry_points={
|
||||
'flask.commands': [
|
||||
'my-command=flask_my_extension.commands:cli'
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
.. _entry point: https://packaging.python.org/tutorials/packaging-projects/#entry-points
|
||||
|
||||
Inside :file:`my_extension/commands.py` you can then export a Click
|
||||
.. _entry point: https://packaging.python.org/tutorials/distributing-packages/#entry-points
|
||||
|
||||
Inside :file:`flask_my_extension/commands.py` you can then export a Click
|
||||
object::
|
||||
|
||||
import click
|
||||
|
|
@ -474,7 +361,7 @@ Custom Scripts
|
|||
--------------
|
||||
|
||||
When you are using the app factory pattern, it may be more convenient to define
|
||||
your own Click script. Instead of using ``--app`` and letting Flask load
|
||||
your own Click script. Instead of using ``FLASK_APP`` and letting Flask load
|
||||
your application, you can create your own Click object and export it as a
|
||||
`console script`_ entry point.
|
||||
|
||||
|
|
@ -493,15 +380,22 @@ Create an instance of :class:`~cli.FlaskGroup` and pass it the factory::
|
|||
def cli():
|
||||
"""Management script for the Wiki application."""
|
||||
|
||||
Define the entry point in :file:`pyproject.toml`:
|
||||
Define the entry point in :file:`setup.py`::
|
||||
|
||||
.. code-block:: toml
|
||||
from setuptools import setup
|
||||
|
||||
[project.scripts]
|
||||
wiki = "wiki:cli"
|
||||
setup(
|
||||
name='flask-my-extension',
|
||||
...,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'wiki=wiki:cli'
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
Install the application in the virtualenv in editable mode and the custom
|
||||
script is available. Note that you don't need to set ``--app``. ::
|
||||
script is available. Note that you don't need to set ``FLASK_APP``. ::
|
||||
|
||||
$ pip install -e .
|
||||
$ wiki run
|
||||
|
|
@ -515,42 +409,58 @@ script is available. Note that you don't need to set ``--app``. ::
|
|||
The ``flask`` command, being separate from your code, does not have
|
||||
this issue and is recommended in most cases.
|
||||
|
||||
.. _console script: https://packaging.python.org/tutorials/packaging-projects/#console-scripts
|
||||
.. _console script: https://packaging.python.org/tutorials/distributing-packages/#console-scripts
|
||||
|
||||
|
||||
PyCharm Integration
|
||||
-------------------
|
||||
|
||||
PyCharm Professional provides a special Flask run configuration to run the development
|
||||
server. For the Community Edition, and for other commands besides ``run``, you need to
|
||||
create a custom run configuration. These instructions should be similar for any other
|
||||
IDE you use.
|
||||
Prior to PyCharm 2018.1, the Flask CLI features weren't yet fully
|
||||
integrated into PyCharm. We have to do a few tweaks to get them working
|
||||
smoothly. These instructions should be similar for any other IDE you
|
||||
might want to use.
|
||||
|
||||
In PyCharm, with your project open, click on *Run* from the menu bar and go to *Edit
|
||||
Configurations*. You'll see a screen similar to this:
|
||||
In PyCharm, with your project open, click on *Run* from the menu bar and
|
||||
go to *Edit Configurations*. You'll be greeted by a screen similar to
|
||||
this:
|
||||
|
||||
.. image:: _static/pycharm-run-config.png
|
||||
.. image:: _static/pycharm-runconfig.png
|
||||
:align: center
|
||||
:class: screenshot
|
||||
:alt: Screenshot of PyCharm run configuration.
|
||||
:alt: screenshot of pycharm's run configuration settings
|
||||
|
||||
Once you create a configuration for the ``flask run``, you can copy and change it to
|
||||
call any other command.
|
||||
There's quite a few options to change, but once we've done it for one
|
||||
command, we can easily copy the entire configuration and make a single
|
||||
tweak to give us access to other commands, including any custom ones you
|
||||
may implement yourself.
|
||||
|
||||
Click the *+ (Add New Configuration)* button and select *Python*. Give the configuration
|
||||
a name such as "flask run".
|
||||
Click the + (*Add New Configuration*) button and select *Python*. Give
|
||||
the configuration a good descriptive name such as "Run Flask Server".
|
||||
For the ``flask run`` command, check "Single instance only" since you
|
||||
can't run the server more than once at the same time.
|
||||
|
||||
Click the *Script path* dropdown and change it to *Module name*, then input ``flask``.
|
||||
Select *Module name* from the dropdown (**A**) 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.
|
||||
The *Parameters* field (**B**) is set to the CLI command to execute
|
||||
(with any arguments). In this example we use ``run``, which will run
|
||||
the development server.
|
||||
|
||||
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.
|
||||
You can skip this next step if you're using :ref:`dotenv`. We need to
|
||||
add an environment variable (**C**) to identify our application. Click
|
||||
on the browse button and add an entry with ``FLASK_APP`` on the left and
|
||||
the Python import or file on the right (``hello`` for example).
|
||||
|
||||
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.
|
||||
Next we need to set the working directory (**D**) to be the folder where
|
||||
our application resides.
|
||||
|
||||
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.
|
||||
If you have installed your project as a package in your virtualenv, you
|
||||
may untick the *PYTHONPATH* options (**E**). This will more accurately
|
||||
match how you deploy the app later.
|
||||
|
||||
Click *Apply* to save the configuration, or *OK* to save and close the
|
||||
window. Select the configuration in the main PyCharm window and click
|
||||
the play button next to it to run the server.
|
||||
|
||||
Now that we have a configuration which runs ``flask run`` from within
|
||||
PyCharm, we can copy that configuration and alter the *Script* argument
|
||||
to run a different CLI command, e.g. ``flask shell``.
|
||||
|
|
|
|||
188
docs/conf.py
|
|
@ -1,96 +1,168 @@
|
|||
import packaging.version
|
||||
from pallets_sphinx_themes import get_version
|
||||
from pallets_sphinx_themes import ProjectLink
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import re
|
||||
|
||||
from pallets_sphinx_themes import DocVersion, ProjectLink, get_version
|
||||
|
||||
# Project --------------------------------------------------------------
|
||||
|
||||
project = "Flask"
|
||||
copyright = "2010 Pallets"
|
||||
author = "Pallets"
|
||||
release, version = get_version("Flask")
|
||||
project = 'Flask'
|
||||
copyright = '2010 Pallets Team'
|
||||
author = 'Pallets Team'
|
||||
release, version = get_version('Flask')
|
||||
|
||||
# General --------------------------------------------------------------
|
||||
|
||||
default_role = "code"
|
||||
master_doc = 'index'
|
||||
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.extlinks",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinxcontrib.log_cabinet",
|
||||
"sphinx_tabs.tabs",
|
||||
"pallets_sphinx_themes",
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinxcontrib.log_cabinet',
|
||||
]
|
||||
autodoc_member_order = "bysource"
|
||||
autodoc_typehints = "description"
|
||||
autodoc_preserve_defaults = True
|
||||
extlinks = {
|
||||
"issue": ("https://github.com/pallets/flask/issues/%s", "#%s"),
|
||||
"pr": ("https://github.com/pallets/flask/pull/%s", "#%s"),
|
||||
"ghsa": ("https://github.com/pallets/flask/security/advisories/GHSA-%s", "GHSA-%s"),
|
||||
}
|
||||
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3/", None),
|
||||
"werkzeug": ("https://werkzeug.palletsprojects.com/", None),
|
||||
"click": ("https://click.palletsprojects.com/", None),
|
||||
"jinja": ("https://jinja.palletsprojects.com/", None),
|
||||
"itsdangerous": ("https://itsdangerous.palletsprojects.com/", None),
|
||||
"sqlalchemy": ("https://docs.sqlalchemy.org/", None),
|
||||
"wtforms": ("https://wtforms.readthedocs.io/", None),
|
||||
"blinker": ("https://blinker.readthedocs.io/", None),
|
||||
'python': ('https://docs.python.org/3/', None),
|
||||
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
||||
'click': ('http://click.pocoo.org/', None),
|
||||
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
||||
'itsdangerous': ('https://pythonhosted.org/itsdangerous', None),
|
||||
'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None),
|
||||
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
||||
'blinker': ('https://pythonhosted.org/blinker/', None),
|
||||
}
|
||||
|
||||
# HTML -----------------------------------------------------------------
|
||||
|
||||
html_theme = "flask"
|
||||
html_theme_options = {"index_sidebar_logo": False}
|
||||
html_theme = 'flask'
|
||||
html_context = {
|
||||
"project_links": [
|
||||
ProjectLink("Donate", "https://palletsprojects.com/donate"),
|
||||
ProjectLink("PyPI Releases", "https://pypi.org/project/Flask/"),
|
||||
ProjectLink("Source Code", "https://github.com/pallets/flask/"),
|
||||
ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"),
|
||||
ProjectLink("Chat", "https://discord.gg/pallets"),
|
||||
]
|
||||
'project_links': [
|
||||
ProjectLink('Donate to Pallets', 'https://psfmember.org/civicrm/contribute/transact?reset=1&id=20'),
|
||||
ProjectLink('Flask Website', 'https://palletsprojects.com/p/flask/'),
|
||||
ProjectLink('PyPI releases', 'https://pypi.org/project/Flask/'),
|
||||
ProjectLink('Source Code', 'https://github.com/pallets/flask/'),
|
||||
ProjectLink(
|
||||
'Issue Tracker', 'https://github.com/pallets/flask/issues/'),
|
||||
],
|
||||
'versions': [
|
||||
DocVersion('dev', 'Development', 'unstable'),
|
||||
DocVersion('1.0', 'Flask 1.0', 'stable'),
|
||||
DocVersion('0.12', 'Flask 0.12'),
|
||||
],
|
||||
'canonical_url': 'http://flask.pocoo.org/docs/{}/'.format(version),
|
||||
'carbon_ads_args': 'zoneid=1673&serve=C6AILKT&placement=pocooorg',
|
||||
}
|
||||
html_sidebars = {
|
||||
"index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"],
|
||||
"**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"],
|
||||
'index': [
|
||||
'project.html',
|
||||
'versions.html',
|
||||
'carbon_ads.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
|
||||
|
||||
gettext_uuid = True
|
||||
gettext_compact = False
|
||||
# LaTeX ----------------------------------------------------------------
|
||||
|
||||
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 -----------------------------------------------------
|
||||
|
||||
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
|
||||
release = app.config.release
|
||||
base_url = "https://github.com/pallets/flask/tree/"
|
||||
base_url = 'https://github.com/pallets/flask/tree/'
|
||||
|
||||
if text.endswith(">"):
|
||||
words, text = text[:-1].rsplit("<", 1)
|
||||
if text.endswith('>'):
|
||||
words, text = text[:-1].rsplit('<', 1)
|
||||
words = words.strip()
|
||||
else:
|
||||
words = None
|
||||
|
||||
if packaging.version.parse(release).is_devrelease:
|
||||
url = f"{base_url}main/{text}"
|
||||
if release.endswith('dev'):
|
||||
url = '{0}master/{1}'.format(base_url, text)
|
||||
else:
|
||||
url = f"{base_url}{release}/{text}"
|
||||
url = '{0}{1}/{2}'.format(base_url, release, text)
|
||||
|
||||
if words is None:
|
||||
words = url
|
||||
|
||||
from docutils.nodes import reference
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
|
||||
options = options or {}
|
||||
set_classes(options)
|
||||
node = reference(rawtext, words, refuri=url, **options)
|
||||
|
|
@ -98,4 +170,6 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None
|
|||
|
||||
|
||||
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)
|
||||
|
|
|
|||
535
docs/config.rst
|
|
@ -1,3 +1,5 @@
|
|||
.. _config:
|
||||
|
||||
Configuration Handling
|
||||
======================
|
||||
|
||||
|
|
@ -7,7 +9,7 @@ toggling the debug mode, setting the secret key, and other such
|
|||
environment-specific things.
|
||||
|
||||
The way Flask is designed usually requires the configuration to be
|
||||
available when the application starts up. You can hard code the
|
||||
available when the application starts up. You can hardcode the
|
||||
configuration in the code, which for many small applications is not
|
||||
actually that bad, but there are better ways.
|
||||
|
||||
|
|
@ -38,26 +40,45 @@ method::
|
|||
|
||||
app.config.update(
|
||||
TESTING=True,
|
||||
SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/'
|
||||
)
|
||||
|
||||
|
||||
Debug Mode
|
||||
----------
|
||||
Environment and Debug Features
|
||||
------------------------------
|
||||
|
||||
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.
|
||||
The :data:`ENV` and :data:`DEBUG` config values are special because they
|
||||
may behave inconsistently if changed after the app has begun setting up.
|
||||
In order to set the environment and debug mode reliably, Flask uses
|
||||
environment variables.
|
||||
|
||||
.. code-block:: text
|
||||
The environment is used to indicate to Flask, extensions, and other
|
||||
programs, like Sentry, what context Flask is running in. It is
|
||||
controlled with the :envvar:`FLASK_ENV` environment variable and
|
||||
defaults to ``production``.
|
||||
|
||||
$ flask --app hello run --debug
|
||||
Setting :envvar:`FLASK_ENV` to ``development`` will enable debug mode.
|
||||
``flask run`` will use the interactive debugger and reloader by default
|
||||
in debug mode. To control this separately from the environment, use the
|
||||
:envvar:`FLASK_DEBUG` flag.
|
||||
|
||||
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.
|
||||
.. versionchanged:: 1.0
|
||||
Added :envvar:`FLASK_ENV` to control the environment separately
|
||||
from debug mode. The development environment enables debug mode.
|
||||
|
||||
To switch Flask to the development environment and enable debug mode,
|
||||
set :envvar:`FLASK_ENV`::
|
||||
|
||||
$ export FLASK_ENV=development
|
||||
$ flask run
|
||||
|
||||
(On Windows, use ``set`` instead of ``export``.)
|
||||
|
||||
Using the environment variables as described above is recommended. While
|
||||
it is possible to set :data:`ENV` and :data:`DEBUG` in your config or
|
||||
code, this is strongly discouraged. They can't be read early by the
|
||||
``flask`` command, and some systems or extensions may have already
|
||||
configured themselves based on a previous value.
|
||||
|
||||
|
||||
Builtin Configuration Values
|
||||
|
|
@ -65,21 +86,38 @@ Builtin Configuration Values
|
|||
|
||||
The following configuration values are used internally by Flask:
|
||||
|
||||
.. py:data:: ENV
|
||||
|
||||
What environment the app is running in. Flask and extensions may
|
||||
enable behaviors based on the environment, such as enabling debug
|
||||
mode. The :attr:`~flask.Flask.env` attribute maps to this config
|
||||
key. This is set by the :envvar:`FLASK_ENV` environment variable and
|
||||
may not behave as expected if set in code.
|
||||
|
||||
**Do not enable development when deploying in production.**
|
||||
|
||||
Default: ``'production'``
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
.. py:data:: DEBUG
|
||||
|
||||
Whether debug mode is enabled. When using ``flask run`` to start the development
|
||||
server, an interactive debugger will be shown for unhandled exceptions, and the
|
||||
server will be reloaded when code changes. The :attr:`~flask.Flask.debug` attribute
|
||||
maps to this config key. This is set with the ``FLASK_DEBUG`` environment variable.
|
||||
It may not behave as expected if set in code.
|
||||
Whether debug mode is enabled. When using ``flask run`` to start the
|
||||
development server, an interactive debugger will be shown for
|
||||
unhandled exceptions, and the server will be reloaded when code
|
||||
changes. The :attr:`~flask.Flask.debug` attribute maps to this
|
||||
config key. This is enabled when :data:`ENV` is ``'development'``
|
||||
and is overridden by the ``FLASK_DEBUG`` environment variable. It
|
||||
may not behave as expected if set in code.
|
||||
|
||||
**Do not enable debug mode when deploying in production.**
|
||||
|
||||
Default: ``False``
|
||||
Default: ``True`` if :data:`ENV` is ``'development'``, or ``False``
|
||||
otherwise.
|
||||
|
||||
.. py:data:: TESTING
|
||||
|
||||
Enable testing mode. Exceptions are propagated rather than handled by
|
||||
Enable testing mode. Exceptions are propagated rather than handled by the
|
||||
the app's error handlers. Extensions may also change their behavior to
|
||||
facilitate easier testing. You should enable this in your own tests.
|
||||
|
||||
|
|
@ -93,6 +131,14 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: PRESERVE_CONTEXT_ON_EXCEPTION
|
||||
|
||||
Don't pop the request context when an exception occurs. If not set, this
|
||||
is true if ``DEBUG`` is true. This allows debuggers to introspect the
|
||||
request data on errors, and should normally not need to be set directly.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: TRAP_HTTP_EXCEPTIONS
|
||||
|
||||
If there is no handler for an ``HTTPException``-type exception, re-raise it
|
||||
|
|
@ -115,35 +161,16 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
A secret key that will be used for securely signing the session cookie
|
||||
and can be used for any other security related needs by extensions or your
|
||||
application. It should be a long random ``bytes`` or ``str``. For
|
||||
example, copy the output of this to your config::
|
||||
application. It should be a long random string of bytes, although unicode
|
||||
is accepted too. For example, copy the output of this to your config::
|
||||
|
||||
$ python -c 'import secrets; print(secrets.token_hex())'
|
||||
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
python -c 'import os; print(os.urandom(16))'
|
||||
b'_5#y2L"F4Q8z\n\xec]/'
|
||||
|
||||
**Do not reveal the secret key when posting questions or committing code.**
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. py:data:: SECRET_KEY_FALLBACKS
|
||||
|
||||
A list of old secret keys that can still be used for unsigning. This allows
|
||||
a project to implement key rotation without invalidating active sessions or
|
||||
other recently-signed secrets.
|
||||
|
||||
Keys should be removed after an appropriate period of time, as checking each
|
||||
additional key adds some overhead.
|
||||
|
||||
Order should not matter, but the default implementation will test the last
|
||||
key in the list first, so it might make sense to order oldest to newest.
|
||||
|
||||
Flask's built-in secure cookie session supports this. Extensions that use
|
||||
:data:`SECRET_KEY` may not support this yet.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: SESSION_COOKIE_NAME
|
||||
|
||||
The name of the session cookie. Can be changed in case you already have a
|
||||
|
|
@ -153,23 +180,12 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
.. py:data:: SESSION_COOKIE_DOMAIN
|
||||
|
||||
The value of the ``Domain`` parameter on the session cookie. If not set, browsers
|
||||
will only send the cookie to the exact domain it was set from. Otherwise, they
|
||||
will send it to any subdomain of the given value as well.
|
||||
|
||||
Not setting this value is more restricted and secure than setting it.
|
||||
The domain match rule that the session cookie will be valid for. If not
|
||||
set, the cookie will be valid for all subdomains of :data:`SERVER_NAME`.
|
||||
If ``False``, the cookie's domain will not be set.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. warning::
|
||||
If this is changed after the browser created a cookie is created with
|
||||
one setting, it may result in another being created. Browsers may send
|
||||
send both in an undefined order. In that case, you may want to change
|
||||
:data:`SESSION_COOKIE_NAME` as well or otherwise invalidate old sessions.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Not set by default, does not fall back to ``SERVER_NAME``.
|
||||
|
||||
.. py:data:: SESSION_COOKIE_PATH
|
||||
|
||||
The path that the session cookie will be valid for. If not set, the cookie
|
||||
|
|
@ -192,23 +208,6 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
Default: ``False``
|
||||
|
||||
.. py:data:: SESSION_COOKIE_PARTITIONED
|
||||
|
||||
Browsers will send cookies based on the top-level document's domain, rather
|
||||
than only the domain of the document setting the cookie. This prevents third
|
||||
party cookies set in iframes from "leaking" between separate sites.
|
||||
|
||||
Browsers are beginning to disallow non-partitioned third party cookies, so
|
||||
you need to mark your cookies partitioned if you expect them to work in such
|
||||
embedded situations.
|
||||
|
||||
Enabling this implicitly enables :data:`SESSION_COOKIE_SECURE` as well, as
|
||||
it is only valid when served over HTTPS.
|
||||
|
||||
Default: ``False``
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
.. py:data:: SESSION_COOKIE_SAMESITE
|
||||
|
||||
Restrict how cookies are sent with requests from external sites. Can
|
||||
|
|
@ -250,61 +249,34 @@ The following configuration values are used internally by Flask:
|
|||
.. 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``.
|
||||
seconds. Can either 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.
|
||||
: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
|
||||
Default: ``timedelta(hours=12)`` (``43200`` seconds)
|
||||
|
||||
.. py:data:: SERVER_NAME
|
||||
|
||||
Inform the application what host and port it is bound to.
|
||||
Inform the application what host and port it is bound to. Required
|
||||
for subdomain route matching support.
|
||||
|
||||
Must be set if ``subdomain_matching`` is enabled, to be able to extract the
|
||||
subdomain from the request.
|
||||
If set, will be used for the session cookie domain if
|
||||
:data:`SESSION_COOKIE_DOMAIN` is not set. Modern web browsers will
|
||||
not allow setting cookies for domains without a dot. To use a domain
|
||||
locally, add any names that should route to the app to your
|
||||
``hosts`` file. ::
|
||||
|
||||
Must be set for ``url_for`` to generate external URLs outside of a
|
||||
request context.
|
||||
127.0.0.1 localhost.dev
|
||||
|
||||
If set, ``url_for`` can generate external URLs with only an application
|
||||
context instead 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).
|
||||
web server.
|
||||
|
||||
Will be used for the session cookie path if ``SESSION_COOKIE_PATH`` is not
|
||||
set.
|
||||
|
|
@ -319,53 +291,42 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
.. 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.
|
||||
Don't read more than this many bytes from the incoming request data. If not
|
||||
set and the request does not specify a ``CONTENT_LENGTH``, no data will be
|
||||
read for security.
|
||||
|
||||
Default: ``None``
|
||||
|
||||
.. versionadded:: 0.6
|
||||
.. py:data:: JSON_AS_ASCII
|
||||
|
||||
.. py:data:: MAX_FORM_MEMORY_SIZE
|
||||
Serialize objects to ASCII-encoded JSON. If this is disabled, the JSON
|
||||
will be returned as a Unicode string, or encoded as ``UTF-8`` by
|
||||
``jsonify``. This has security implications when rendering the JSON in
|
||||
to JavaScript in templates, and should typically remain enabled.
|
||||
|
||||
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.
|
||||
Default: ``True``
|
||||
|
||||
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.
|
||||
.. py:data:: JSON_SORT_KEYS
|
||||
|
||||
Default: ``500_000``
|
||||
Sort the keys of JSON objects alphabetically. This is useful for caching
|
||||
because it ensures the data is serialized the same way no matter what
|
||||
Python's hash seed is. While not recommended, you can disable this for a
|
||||
possible performance improvement at the cost of caching.
|
||||
|
||||
.. versionadded:: 3.1
|
||||
Default: ``True``
|
||||
|
||||
.. py:data:: MAX_FORM_PARTS
|
||||
.. py:data:: JSONIFY_PRETTYPRINT_REGULAR
|
||||
|
||||
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.
|
||||
``jsonify`` responses will be output with newlines, spaces, and indentation
|
||||
for easier reading by humans. Always enabled in debug mode.
|
||||
|
||||
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: ``False``
|
||||
|
||||
Default: ``1_000``
|
||||
.. py:data:: JSONIFY_MIMETYPE
|
||||
|
||||
.. versionadded:: 3.1
|
||||
The mimetype of ``jsonify`` responses.
|
||||
|
||||
Default: ``'application/json'``
|
||||
|
||||
.. py:data:: TEMPLATES_AUTO_RELOAD
|
||||
|
||||
|
|
@ -388,12 +349,6 @@ The following configuration values are used internally by Flask:
|
|||
``4093``. Larger cookies may be silently ignored by browsers. Set to
|
||||
``0`` to disable the warning.
|
||||
|
||||
.. py:data:: PROVIDE_AUTOMATIC_OPTIONS
|
||||
|
||||
Set to ``False`` to disable the automatic addition of OPTIONS
|
||||
responses. This can be overridden per route by altering the
|
||||
``provide_automatic_options`` attribute.
|
||||
|
||||
.. versionadded:: 0.4
|
||||
``LOGGER_NAME``
|
||||
|
||||
|
|
@ -424,7 +379,7 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
.. versionchanged:: 1.0
|
||||
``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See
|
||||
:doc:`/logging` for information about configuration.
|
||||
:ref:`logging` for information about configuration.
|
||||
|
||||
Added :data:`ENV` to reflect the :envvar:`FLASK_ENV` environment
|
||||
variable.
|
||||
|
|
@ -434,30 +389,17 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
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.
|
||||
Configuring from Files
|
||||
----------------------
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
``ENV`` was removed.
|
||||
Configuration becomes more useful if you can store it in a separate file,
|
||||
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.
|
||||
|
||||
.. 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::
|
||||
So a common pattern is this::
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('yourapplication.default_settings')
|
||||
|
|
@ -466,42 +408,18 @@ A common pattern is this::
|
|||
This first loads the configuration from the
|
||||
`yourapplication.default_settings` module and then overrides the values
|
||||
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||
environment variable points to. This environment variable can be set
|
||||
in the shell before starting the server:
|
||||
environment variable points to. This environment variable can be set on
|
||||
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
|
||||
|
||||
$ 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/
|
||||
>set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg
|
||||
|
||||
The configuration files themselves are actual Python files. Only values
|
||||
in uppercase are actually stored in the config object later on. So make
|
||||
|
|
@ -510,7 +428,8 @@ sure to use uppercase letters for your config keys.
|
|||
Here is an example of a configuration file::
|
||||
|
||||
# Example configuration
|
||||
SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'
|
||||
DEBUG = False
|
||||
SECRET_KEY = b'_5#y2L"F4Q8z\n\xec]/'
|
||||
|
||||
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
|
||||
|
|
@ -518,118 +437,54 @@ methods on the config object as well to load from individual files. For a
|
|||
complete reference, read the :class:`~flask.Config` object's
|
||||
documentation.
|
||||
|
||||
|
||||
Configuring from Data Files
|
||||
---------------------------
|
||||
|
||||
It is also possible to load configuration from a file in a format of
|
||||
your choice using :meth:`~flask.Config.from_file`. For example to load
|
||||
from a TOML file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import tomllib
|
||||
app.config.from_file("config.toml", load=tomllib.load, text=False)
|
||||
|
||||
Or from a JSON file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import json
|
||||
app.config.from_file("config.json", load=json.load)
|
||||
|
||||
|
||||
Configuring from Environment Variables
|
||||
--------------------------------------
|
||||
|
||||
In addition to pointing to configuration files using environment
|
||||
variables, you may find it useful (or necessary) to control your
|
||||
configuration values directly from the environment. Flask can be
|
||||
instructed to load all environment variables starting with a specific
|
||||
prefix into the config using :meth:`~flask.Config.from_prefixed_env`.
|
||||
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.
|
||||
|
||||
Environment variables can be set in the shell before starting the
|
||||
server:
|
||||
Environment variables can be set on Linux or OS X with the export command in
|
||||
the shell before starting the server::
|
||||
|
||||
.. tabs::
|
||||
$ export SECRET_KEY='5f352379324c22463451387a0aec5d2f'
|
||||
$ export DEBUG=False
|
||||
$ 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 SECRET_KEY='5f352379324c22463451387a0aec5d2f'
|
||||
>set DEBUG=False
|
||||
|
||||
$ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f"
|
||||
$ export FLASK_MAIL_ENABLED=false
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
While this approach is straightforward to use, it is important to remember that
|
||||
environment variables are strings -- they are not automatically deserialized
|
||||
into Python types.
|
||||
|
||||
.. group-tab:: Fish
|
||||
Here is an example of a configuration file that uses environment variables::
|
||||
|
||||
.. code-block:: text
|
||||
# Example configuration
|
||||
import os
|
||||
|
||||
$ set -x FLASK_SECRET_KEY "5f352379324c22463451387a0aec5d2f"
|
||||
$ set -x FLASK_MAIL_ENABLED false
|
||||
$ flask run
|
||||
* Running on http://127.0.0.1:5000/
|
||||
ENVIRONMENT_DEBUG = os.environ.get("DEBUG", default=False)
|
||||
if ENVIRONMENT_DEBUG.lower() in ("f", "false"):
|
||||
ENVIRONMENT_DEBUG = False
|
||||
|
||||
.. group-tab:: CMD
|
||||
DEBUG = ENVIRONMENT_DEBUG
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", default=None)
|
||||
if not SECRET_KEY:
|
||||
raise ValueError("No secret key set for Flask application")
|
||||
|
||||
.. 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/
|
||||
Notice that any value besides an empty string will be interpreted as a boolean
|
||||
``True`` value in Python, which requires care if an environment explicitly sets
|
||||
values intended to be ``False``.
|
||||
|
||||
Make sure to load the configuration very early on, so that extensions have the
|
||||
ability to access the configuration when starting up. There are other methods
|
||||
on the config object as well to load from individual files. For a complete
|
||||
reference, read the :class:`~flask.Config` class documentation.
|
||||
|
||||
Configuration Best Practices
|
||||
----------------------------
|
||||
|
|
@ -641,17 +496,13 @@ that experience:
|
|||
|
||||
1. Create your application in a function and register blueprints on it.
|
||||
That way you can create multiple instances of your application with
|
||||
different configurations attached which makes unit testing a lot
|
||||
different configurations attached which makes unittesting a lot
|
||||
easier. You can use this to pass in configuration as needed.
|
||||
|
||||
2. Do not write code that needs the configuration at import time. If you
|
||||
limit yourself to request-only accesses to the configuration you can
|
||||
reconfigure the object later on as needed.
|
||||
|
||||
3. Make sure to load the configuration very early on, so that
|
||||
extensions can access the configuration when calling ``init_app``.
|
||||
|
||||
|
||||
.. _config-dev-prod:
|
||||
|
||||
Development / Production
|
||||
|
|
@ -678,22 +529,23 @@ the config file by adding ``from yourapplication.default_settings
|
|||
import *`` to the top of the file and then overriding the changes by hand.
|
||||
You could also inspect an environment variable like
|
||||
``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc
|
||||
and import different hard-coded files based on that.
|
||||
and import different hardcoded files based on that.
|
||||
|
||||
An interesting pattern is also to use classes and inheritance for
|
||||
configuration::
|
||||
|
||||
class Config(object):
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
DATABASE_URI = 'sqlite:///:memory:'
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DATABASE_URI = 'mysql://user@localhost/foo'
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DATABASE_URI = "sqlite:////tmp/foo.db"
|
||||
DEBUG = True
|
||||
|
||||
class TestingConfig(Config):
|
||||
DATABASE_URI = 'sqlite:///:memory:'
|
||||
TESTING = True
|
||||
|
||||
To enable such a config you just have to call into
|
||||
|
|
@ -701,41 +553,6 @@ To enable such a config you just have to call into
|
|||
|
||||
app.config.from_object('configmodule.ProductionConfig')
|
||||
|
||||
Note that :meth:`~flask.Config.from_object` does not instantiate the class
|
||||
object. If you need to instantiate the class, such as to access a property,
|
||||
then you must do so before calling :meth:`~flask.Config.from_object`::
|
||||
|
||||
from configmodule import ProductionConfig
|
||||
app.config.from_object(ProductionConfig())
|
||||
|
||||
# Alternatively, import via string:
|
||||
from werkzeug.utils import import_string
|
||||
cfg = import_string('configmodule.ProductionConfig')()
|
||||
app.config.from_object(cfg)
|
||||
|
||||
Instantiating the configuration object allows you to use ``@property`` in
|
||||
your configuration classes::
|
||||
|
||||
class Config(object):
|
||||
"""Base config, uses staging database server."""
|
||||
TESTING = False
|
||||
DB_SERVER = '192.168.1.56'
|
||||
|
||||
@property
|
||||
def DATABASE_URI(self): # Note: all caps
|
||||
return f"mysql://user@{self.DB_SERVER}/foo"
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""Uses production database server."""
|
||||
DB_SERVER = '192.168.19.32'
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DB_SERVER = 'localhost'
|
||||
|
||||
class TestingConfig(Config):
|
||||
DB_SERVER = 'localhost'
|
||||
DATABASE_URI = 'sqlite:///:memory:'
|
||||
|
||||
There are many different ways and it's up to you how you want to manage
|
||||
your configuration files. However here a list of good recommendations:
|
||||
|
||||
|
|
@ -749,10 +566,12 @@ your configuration files. However here a list of good recommendations:
|
|||
code at all. If you are working often on different projects you can
|
||||
even create your own script for sourcing that activates a virtualenv
|
||||
and exports the development configuration for you.
|
||||
- Use a tool like `fabric`_ to push code and configuration separately
|
||||
to the production server(s).
|
||||
- Use a tool like `fabric`_ in production to push code and
|
||||
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:
|
||||
|
|
@ -800,7 +619,7 @@ locations are used:
|
|||
|
||||
- Installed module or package::
|
||||
|
||||
$PREFIX/lib/pythonX.Y/site-packages/myapp
|
||||
$PREFIX/lib/python2.X/site-packages/myapp
|
||||
$PREFIX/var/myapp-instance
|
||||
|
||||
``$PREFIX`` is the prefix of your Python installation. This can be
|
||||
|
|
@ -817,7 +636,7 @@ root” (the default) to “relative to instance folder” via the
|
|||
app = Flask(__name__, instance_relative_config=True)
|
||||
|
||||
Here is a full example of how to configure Flask to preload the config
|
||||
from a module and then override the config from a file in the instance
|
||||
from a module and then override the config from a file in the config
|
||||
folder if it exists::
|
||||
|
||||
app = Flask(__name__, instance_relative_config=True)
|
||||
|
|
|
|||
62
docs/contents.rst.inc
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
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
|
||||
logging
|
||||
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
|
||||
upgrading
|
||||
changelog
|
||||
license
|
||||
contributing
|
||||
|
|
@ -1,8 +1 @@
|
|||
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/
|
||||
.. include:: ../CONTRIBUTING.rst
|
||||
|
|
|
|||
|
|
@ -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/python<your_python_version>/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
|
||||
<https://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: https://nginx.org/
|
||||
.. _lighttpd: https://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,38 @@
|
|||
Deploying to Production
|
||||
=======================
|
||||
.. _deployment:
|
||||
|
||||
After developing your application, you'll want to make it available
|
||||
publicly to other users. When you're developing locally, you're probably
|
||||
using the built-in development server, debugger, and reloader. These
|
||||
should not be used in production. Instead, you should use a dedicated
|
||||
WSGI server or hosting platform, some of which will be described here.
|
||||
Deployment Options
|
||||
==================
|
||||
|
||||
"Production" means "not development", which applies whether you're
|
||||
serving your application publicly to millions of users or privately /
|
||||
locally to a single user. **Do not use the development server when
|
||||
deploying to production. It is intended for use only during local
|
||||
development. It is not designed to be particularly secure, stable, or
|
||||
efficient.**
|
||||
While lightweight and easy to use, **Flask's built-in server is not suitable
|
||||
for production** as it doesn't scale well. Some of the options available for
|
||||
properly running Flask in production are documented here.
|
||||
|
||||
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://cloud.google.com/appengine/docs/standard/python/getting-started/python-standard-env>`_
|
||||
- `Deploying Flask on AWS Elastic Beanstalk <https://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::
|
||||
:maxdepth: 1
|
||||
:maxdepth: 2
|
||||
|
||||
gunicorn
|
||||
waitress
|
||||
mod_wsgi
|
||||
uwsgi
|
||||
gevent
|
||||
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.
|
||||
wsgi-standalone
|
||||
uwsgi
|
||||
mod_wsgi
|
||||
fastcgi
|
||||
cgi
|
||||
|
|
|
|||
|
|
@ -1,94 +1,217 @@
|
|||
mod_wsgi
|
||||
========
|
||||
.. _mod_wsgi-deployment:
|
||||
|
||||
`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server.
|
||||
The modern `mod_wsgi-express`_ command makes it easy to configure and
|
||||
start the server without needing to write Apache httpd configuration.
|
||||
mod_wsgi (Apache)
|
||||
=================
|
||||
|
||||
* Tightly integrated with Apache httpd.
|
||||
* Supports Windows directly.
|
||||
* Requires a compiler and the Apache development headers to install.
|
||||
* Does not require a reverse proxy setup.
|
||||
If you are using the `Apache`_ webserver, consider using `mod_wsgi`_.
|
||||
|
||||
This page outlines the basics of running mod_wsgi-express, not the more
|
||||
complex installation and configuration with httpd. Be sure to read the
|
||||
`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to
|
||||
understand what features are available.
|
||||
.. admonition:: Watch Out
|
||||
|
||||
.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/
|
||||
.. _mod_wsgi: https://modwsgi.readthedocs.io/
|
||||
.. _Apache httpd: https://httpd.apache.org/
|
||||
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 mod_wsgi.
|
||||
|
||||
.. _Apache: https://httpd.apache.org/
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing `mod_wsgi`
|
||||
---------------------
|
||||
|
||||
Installing mod_wsgi requires a compiler and the Apache server and
|
||||
development headers installed. You will get an error if they are not.
|
||||
How to install them depends on the OS and package manager that you use.
|
||||
If you don't have `mod_wsgi` installed yet you have to either install it
|
||||
using a package manager or compile it yourself. The mod_wsgi
|
||||
`installation instructions`_ cover source installations on UNIX systems.
|
||||
|
||||
Create a virtualenv, install your application, then install
|
||||
``mod_wsgi``.
|
||||
If you are using Ubuntu/Debian you can apt-get it and activate it as
|
||||
follows:
|
||||
|
||||
.. code-block:: text
|
||||
.. sourcecode:: text
|
||||
|
||||
$ cd hello-app
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install mod_wsgi
|
||||
# apt-get install libapache2-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
|
||||
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.
|
||||
# yum install mod_wsgi
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by
|
||||
using pkg_add:
|
||||
|
||||
from hello import app
|
||||
.. sourcecode:: text
|
||||
|
||||
application = app
|
||||
# pkg install ap22-mod_wsgi2
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
If you are using pkgsrc you can install `mod_wsgi` by compiling the
|
||||
`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
|
||||
run; a starting value could be ``CPU * 2``.
|
||||
If you don't have a factory function for application creation but a singleton
|
||||
instance you can directly import that one as `application`.
|
||||
|
||||
Logs for each request aren't show in the terminal. If an error occurs,
|
||||
its information is written to the error log file shown when starting the
|
||||
server.
|
||||
Store that file somewhere that you will find it again (e.g.:
|
||||
:file:`/var/www/yourapplication`) and make sure that `yourapplication` and all
|
||||
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
|
||||
root to bind to privileged ports like 80 and 443. However, it must be
|
||||
configured to drop permissions to a different user and group for the
|
||||
worker processes.
|
||||
The last thing you have to do is to create an Apache configuration file
|
||||
for your application. In this example we are telling `mod_wsgi` to
|
||||
execute the application under a different user for security reasons:
|
||||
|
||||
For example, if you created a ``hello`` user and group, you should
|
||||
install your virtualenv and application as that user, then tell
|
||||
mod_wsgi to drop to that user after starting.
|
||||
.. sourcecode:: apache
|
||||
|
||||
.. code-block:: text
|
||||
<VirtualHost *>
|
||||
ServerName example.com
|
||||
|
||||
$ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \
|
||||
/home/hello/wsgi.py \
|
||||
--user hello --group hello --port 80 --processes 4
|
||||
WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5
|
||||
WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi
|
||||
|
||||
<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: https://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: https://modwsgi.readthedocs.io/en/develop/installation.html
|
||||
.. _virtual python: https://pypi.org/project/virtualenv/
|
||||
.. _mod_wsgi documentation: https://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`_ is a fast, compiled server suite with extensive configuration
|
||||
and capabilities beyond a basic server.
|
||||
uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||
`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.
|
||||
* It is complex to configure beyond the basic application, and has so
|
||||
many options that it can be difficult for beginners to understand.
|
||||
* It does not support Windows (but does run on WSL).
|
||||
* It requires a compiler to install in some cases.
|
||||
The most popular uWSGI server is `uwsgi`_, which we will use for this
|
||||
guide. Make sure to have it installed to follow along.
|
||||
|
||||
This page outlines the basics of running uWSGI. Be sure to read its
|
||||
documentation to understand what features are available.
|
||||
.. admonition:: Watch Out
|
||||
|
||||
.. _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
|
||||
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.
|
||||
Given a flask application in myapp.py, use the following command:
|
||||
|
||||
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
|
||||
$ python -m venv .venv
|
||||
$ . .venv/bin/activate
|
||||
$ pip install . # install your application
|
||||
$ pip install pyuwsgi
|
||||
The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi,
|
||||
since its smarter about that. It is used together with the ``--mount`` directive
|
||||
which will make requests to ``/yourapplication`` be directed to ``myapp:app``.
|
||||
If your application is accessible at root level, you can use a single ``/``
|
||||
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
|
||||
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
|
||||
Either method will include SSL support.
|
||||
If you want to deploy your flask application inside of a virtual environment,
|
||||
you need to also add ``--virtualenv /path/to/virtual/environment``. You might
|
||||
also need to add ``--plugin python`` or ``--plugin python3`` depending on which
|
||||
python version you use for your project.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ pip install uwsgi
|
||||
|
||||
# or
|
||||
$ pip install --no-binary pyuwsgi pyuwsgi
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
The most basic way to run uWSGI is to tell it to start an HTTP server
|
||||
and import your application.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app
|
||||
|
||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
||||
*** Operational MODE: preforking ***
|
||||
mounting hello:app on /
|
||||
spawned uWSGI master process (pid: x)
|
||||
spawned uWSGI worker 1 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 2 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 3 (pid: x, cores: 1)
|
||||
spawned uWSGI worker 4 (pid: x, cores: 1)
|
||||
spawned uWSGI http 1 (pid: x)
|
||||
|
||||
If you're using the app factory pattern, you'll need to create a small
|
||||
Python file to create the app, then point uWSGI at that.
|
||||
|
||||
.. code-block:: python
|
||||
:caption: ``wsgi.py``
|
||||
|
||||
from hello import create_app
|
||||
|
||||
app = create_app()
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app
|
||||
|
||||
The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The
|
||||
``--master`` option specifies the standard worker manager. The ``-p``
|
||||
option starts 4 worker processes; a starting value could be ``CPU * 2``.
|
||||
The ``-w`` option tells uWSGI how to import your application
|
||||
|
||||
|
||||
Binding Externally
|
||||
------------------
|
||||
|
||||
uWSGI should not be run as root with the configuration shown in this doc
|
||||
because it would cause your application code to run as root, which is
|
||||
not secure. However, this means it will not be possible to bind to port
|
||||
80 or 443. Instead, a reverse proxy such as :doc:`nginx` or
|
||||
:doc:`apache-httpd` should be used in front of uWSGI. It is possible to
|
||||
run uWSGI as root securely, but that is beyond the scope of this doc.
|
||||
|
||||
uWSGI has optimized integration with `Nginx uWSGI`_ and
|
||||
`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using
|
||||
a standard HTTP proxy. That configuration is beyond the scope of this
|
||||
doc, see the links for more information.
|
||||
|
||||
.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
|
||||
.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
|
||||
|
||||
You can bind to all external IPs on a non-privileged port using the
|
||||
``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy
|
||||
setup, otherwise it will be possible to bypass the proxy.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app
|
||||
|
||||
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
|
||||
IP address in your browser.
|
||||
|
||||
|
||||
Async with gevent
|
||||
Configuring nginx
|
||||
-----------------
|
||||
|
||||
The default sync worker is appropriate for most use cases. If you need numerous,
|
||||
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.
|
||||
A basic flask nginx configuration looks like this::
|
||||
|
||||
.. _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
|
||||
required.
|
||||
This configuration binds the application to ``/yourapplication``. If you want
|
||||
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
|
||||
|
||||
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
|
||||
*** Operational MODE: async ***
|
||||
mounting hello:app on /
|
||||
spawned uWSGI master process (pid: x)
|
||||
spawned uWSGI worker 1 (pid: x, cores: 100)
|
||||
spawned uWSGI http 1 (pid: x)
|
||||
*** running gevent loop engine [addr:x] ***
|
||||
.. _nginx: https://nginx.org/
|
||||
.. _lighttpd: https://www.lighttpd.net/
|
||||
.. _cherokee: http://cherokee-project.com/
|
||||
.. _uwsgi: http://projects.unbit.it/uwsgi/
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
150
docs/deploying/wsgi-standalone.rst
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
.. _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/
|
||||
|
||||
uWSGI
|
||||
--------
|
||||
|
||||
`uWSGI`_ is a fast application server written in C. It is very configurable
|
||||
which makes it more complicated to setup than gunicorn.
|
||||
|
||||
Running `uWSGI HTTP Router`_::
|
||||
|
||||
uwsgi --http 127.0.0.1:5000 --module myproject:app
|
||||
|
||||
For a more optimized setup, see `configuring uWSGI and NGINX`_.
|
||||
|
||||
.. _uWSGI: http://uwsgi-docs.readthedocs.io/en/latest/
|
||||
.. _uWSGI HTTP Router: http://uwsgi-docs.readthedocs.io/en/latest/HTTP.html#the-uwsgi-http-https-router
|
||||
.. _configuring uWSGI and NGINX: uwsgi.html#starting-your-app-with-uwsgi
|
||||
|
||||
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 tcp: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
|
||||
=========================
|
||||
|
||||
|
|
@ -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
|
||||
causes some problems I won't outline here in detail. Now the question is:
|
||||
when does a microframework need more than one application at the same
|
||||
time? A good example for this is unit testing. When you want to test
|
||||
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
|
||||
specific behavior. When the application object is deleted everything it
|
||||
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`).
|
||||
|
||||
Furthermore this design makes it possible to use a factory function to
|
||||
create the application which is very helpful for unit testing and similar
|
||||
things (:doc:`/patterns/appfactories`).
|
||||
create the application which is very helpful for unittesting and similar
|
||||
things (:ref:`app-factories`).
|
||||
|
||||
The Routing System
|
||||
------------------
|
||||
|
|
@ -96,10 +98,10 @@ is ambiguous.
|
|||
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
|
||||
template engine, but Flask will still configure Jinja for you. While
|
||||
that limitation that Jinja is *always* configured will probably go away,
|
||||
template engine, but Flask will still configure Jinja2 for you. While
|
||||
that limitation that Jinja2 is *always* configured will probably go away,
|
||||
the decision to bundle one template engine and use that will not.
|
||||
|
||||
Template engines are like programming languages and each of those engines
|
||||
|
|
@ -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
|
||||
of variables and take the return value as string.
|
||||
|
||||
But that's about where similarities end. Jinja for example has an
|
||||
extensive filter system, a certain way to do template inheritance,
|
||||
support for reusable blocks (macros) that can be used from inside
|
||||
templates and also from Python code, supports iterative template
|
||||
rendering, configurable syntax and more. On the other hand an engine
|
||||
like Genshi is based on XML stream evaluation, template inheritance by
|
||||
taking the availability of XPath into account and more. Mako on the
|
||||
other hand treats templates similar to Python modules.
|
||||
But that's about where similarities end. Jinja2 for example has an
|
||||
extensive filter system, a certain way to do template inheritance, support
|
||||
for reusable blocks (macros) that can be used from inside templates and
|
||||
also from Python code, uses Unicode for all operations, supports
|
||||
iterative template rendering, configurable syntax and more. On the other
|
||||
hand an engine like Genshi is based on XML stream evaluation, template
|
||||
inheritance by taking the availability of XPath into account and more.
|
||||
Mako on the other hand treats templates similar to Python modules.
|
||||
|
||||
When it comes to connecting a template engine with an application or
|
||||
framework there is more than just rendering templates. For instance,
|
||||
Flask uses Jinja's extensive autoescaping support. Also it provides
|
||||
ways to access macros from Jinja templates.
|
||||
Flask uses Jinja2's extensive autoescaping support. Also it provides
|
||||
ways to access macros from Jinja2 templates.
|
||||
|
||||
A template abstraction layer that would not take the unique features of
|
||||
the template engines away is a science on its own and a too large
|
||||
|
|
@ -130,27 +132,11 @@ being present. You can easily use your own templating language, but an
|
|||
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
|
||||
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
|
||||
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
|
||||
|
|
@ -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.
|
||||
|
||||
|
||||
Context Locals
|
||||
--------------
|
||||
Thread Locals
|
||||
-------------
|
||||
|
||||
Flask uses special context locals and proxies to provide access to the
|
||||
current app and request data to any code running during a request, CLI command,
|
||||
etc. Context locals are specific to the worker handling the activity, such as a
|
||||
thread, process, coroutine, or greenlet.
|
||||
Flask uses thread local objects (context local objects in fact, they
|
||||
support greenlet contexts as well) for request, session and an extra
|
||||
object you can put your own things on (:data:`~flask.g`). Why is that and
|
||||
isn't that a bad idea?
|
||||
|
||||
The context and proxies help solve two development issues: circular imports, and
|
||||
passing around global data. :data:`.current_app` can be used to access the
|
||||
application object without needing to import the app object directly, avoiding
|
||||
circular import issues. :data:`.request`, :data:`.session`, and :data:`.g` can
|
||||
be imported to access the current data for the request, rather than needing to
|
||||
pass them as arguments through every single function in your project.
|
||||
Yes it is usually not such a bright idea to use thread locals. They cause
|
||||
troubles for servers that are not based on the concept of threads and make
|
||||
large applications harder to maintain. However Flask is just not designed
|
||||
for large applications or asynchronous servers. Flask wants to make it
|
||||
quick and easy to write a traditional web application.
|
||||
|
||||
|
||||
Async/await and ASGI support
|
||||
----------------------------
|
||||
|
||||
Flask supports ``async`` coroutines for view functions by executing the
|
||||
coroutine on a separate thread instead of using an event loop on the
|
||||
main thread as an async-first (ASGI) framework would. This is necessary
|
||||
for Flask to remain backwards compatible with extensions and code built
|
||||
before ``async`` was introduced into Python. This compromise introduces
|
||||
a performance cost compared with the ASGI frameworks, due to the
|
||||
overhead of the threads.
|
||||
|
||||
Due to how tied to WSGI Flask's code is, it's not clear if it's possible
|
||||
to make the ``Flask`` class support ASGI and WSGI at the same time. Work
|
||||
is currently being done in Werkzeug to work with ASGI, which may
|
||||
eventually enable support in Flask as well.
|
||||
|
||||
See :doc:`/async-await` for more discussion.
|
||||
Also see the :ref:`becomingbig` section of the documentation for some
|
||||
inspiration for larger applications based on Flask.
|
||||
|
||||
|
||||
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
|
||||
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.
|
||||
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
|
||||
engine in some sort. However not every application needs a SQL database.
|
||||
|
||||
As your codebase grows, you are free to make the design decisions appropriate
|
||||
for your project. Flask will continue to provide a very simple glue layer to
|
||||
the best that Python has to offer. You can implement advanced patterns in
|
||||
SQLAlchemy or another database tool, introduce non-relational data persistence
|
||||
as appropriate, and take advantage of framework-agnostic tools built for WSGI,
|
||||
the Python web interface.
|
||||
|
||||
The idea of Flask is to build a good foundation for all applications.
|
||||
Everything else is up to you or extensions.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
Handling Application Errors
|
||||
===========================
|
||||
.. _application-errors:
|
||||
|
||||
Applications fail, servers fail. Sooner or later you will see an exception
|
||||
in production. Even if your code is 100% correct, you will still see
|
||||
exceptions from time to time. Why? Because everything else involved will
|
||||
fail. Here are some situations where perfectly fine code can lead to server
|
||||
Application Errors
|
||||
==================
|
||||
|
||||
.. 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:
|
||||
|
||||
- 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
|
||||
- network connection of the server to another system failed
|
||||
|
||||
And that's just a small sample of issues you could be facing. So how do we
|
||||
deal with that sort of problem? By default if your application runs in
|
||||
production mode, and an exception is raised Flask will display a very simple
|
||||
page for you and log the exception to the :attr:`~flask.Flask.logger`.
|
||||
And that's just a small sample of issues you could be facing. So how do we
|
||||
deal with that sort of problem? By default if your application runs in
|
||||
production mode, Flask will display a very simple page for you and log the
|
||||
exception to the :attr:`~flask.Flask.logger`.
|
||||
|
||||
But there is more you can do, and we will cover some better setups to deal
|
||||
with errors including custom exceptions and 3rd party tools.
|
||||
|
||||
|
||||
.. _error-logging-tools:
|
||||
with errors.
|
||||
|
||||
Error Logging Tools
|
||||
-------------------
|
||||
|
|
@ -33,66 +34,51 @@ Error Logging Tools
|
|||
Sending error mails, even if just for critical ones, can become
|
||||
overwhelming if enough users are hitting the error and log files are
|
||||
typically never looked at. This is why we recommend using `Sentry
|
||||
<https://sentry.io/>`_ for dealing with application errors. It's
|
||||
available as a source-available project `on GitHub
|
||||
<https://github.com/getsentry/sentry>`_ and is also available as a `hosted version
|
||||
<https://sentry.io/signup/>`_ which you can try for free. Sentry
|
||||
<https://www.getsentry.com/>`_ for dealing with application errors. It's
|
||||
available as an Open Source project `on GitHub
|
||||
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
||||
<https://getsentry.com/signup/>`_ which you can try for free. Sentry
|
||||
aggregates duplicate errors, captures the full stack trace and local
|
||||
variables for debugging, and sends you mails based on new errors or
|
||||
frequency thresholds.
|
||||
|
||||
To use Sentry you need to install the ``sentry-sdk`` client with extra
|
||||
``flask`` dependencies.
|
||||
To use Sentry you need to install the `raven` client with extra `flask` dependencies::
|
||||
|
||||
.. code-block:: text
|
||||
$ pip install raven[flask]
|
||||
|
||||
$ 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 sentry_sdk.integrations.flask import FlaskIntegration
|
||||
from raven.contrib.flask import Sentry
|
||||
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
|
||||
get from your Sentry installation.
|
||||
The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get
|
||||
from your Sentry installation.
|
||||
|
||||
After installation, failures leading to an Internal Server Error
|
||||
are automatically reported to Sentry and from there you can
|
||||
receive error notifications.
|
||||
Afterwards failures are automatically reported to Sentry and from there
|
||||
you can receive error notifications.
|
||||
|
||||
See also:
|
||||
.. _error-handlers:
|
||||
|
||||
- Sentry also supports catching errors from a worker queue
|
||||
(RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs
|
||||
<https://docs.sentry.io/platforms/python/>`__ for more information.
|
||||
- `Flask-specific documentation <https://docs.sentry.io/platforms/python/guides/flask/>`__
|
||||
|
||||
|
||||
Error Handlers
|
||||
Error handlers
|
||||
--------------
|
||||
|
||||
When an error occurs in Flask, an appropriate `HTTP status code
|
||||
<https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>`__ will be
|
||||
returned. 400-499 indicate errors with the client's request data, or
|
||||
about the data requested. 500-599 indicate errors with the server or
|
||||
application itself.
|
||||
|
||||
You might want to show custom error pages to the user when an error occurs.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
An error handler is a function that returns a response when a type of error is
|
||||
raised, similar to how a view is a function that returns a response when a
|
||||
request URL is matched. It is passed the instance of the error being handled,
|
||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`.
|
||||
|
||||
The status code of the response will not be set to the handler's code. Make
|
||||
sure to provide the appropriate HTTP status code when returning a response from
|
||||
a handler.
|
||||
|
||||
An error handler is a normal view function that return a response, but instead
|
||||
of being registered for a route, it is registered for an exception or HTTP
|
||||
status code that would is raised while trying to handle a request.
|
||||
|
||||
Registering
|
||||
```````````
|
||||
|
|
@ -100,9 +86,7 @@ Registering
|
|||
Register handlers by decorating a function with
|
||||
:meth:`~flask.Flask.errorhandler`. Or use
|
||||
:meth:`~flask.Flask.register_error_handler` to register the function later.
|
||||
Remember to set the error code when returning the response.
|
||||
|
||||
.. code-block:: python
|
||||
Remember to set the error code when returning the response. ::
|
||||
|
||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||
def handle_bad_request(e):
|
||||
|
|
@ -118,15 +102,13 @@ when registering handlers. (``BadRequest.code == 400``)
|
|||
Non-standard HTTP codes cannot be registered by code because they are not known
|
||||
by Werkzeug. Instead, define a subclass of
|
||||
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
||||
register and raise that exception class.
|
||||
|
||||
.. code-block:: python
|
||||
register and raise that exception class. ::
|
||||
|
||||
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
||||
code = 507
|
||||
description = 'Not enough storage space.'
|
||||
|
||||
app.register_error_handler(InsufficientStorage, handle_507)
|
||||
app.register_error_handler(InsuffcientStorage, handle_507)
|
||||
|
||||
raise InsufficientStorage()
|
||||
|
||||
|
|
@ -135,36 +117,20 @@ Handlers can be registered for any exception class, not just
|
|||
codes. Handlers can be registered for a specific class, or for all subclasses
|
||||
of a parent class.
|
||||
|
||||
|
||||
Handling
|
||||
````````
|
||||
|
||||
When building a Flask application you *will* run into exceptions. If some part
|
||||
of your code breaks while handling a request (and you have no error handlers
|
||||
registered), a "500 Internal Server Error"
|
||||
(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default.
|
||||
Similarly, "404 Not Found"
|
||||
(:exc:`~werkzeug.exceptions.NotFound`) error will occur if a request is sent to an unregistered route.
|
||||
If a route receives an unallowed request method, a "405 Method Not Allowed"
|
||||
(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all
|
||||
subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by
|
||||
default in Flask.
|
||||
|
||||
Flask gives you the ability to raise any HTTP exception registered by
|
||||
Werkzeug. However, the default HTTP exceptions return simple exception
|
||||
pages. You might want to show custom error pages to the user when an error occurs.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
When Flask catches an exception while handling a request, it is first looked up by code.
|
||||
If no handler is registered for the code, Flask looks up the error by its class hierarchy; the most specific handler is chosen.
|
||||
If no handler is registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||
When an exception is caught by Flask while handling a request, it is first
|
||||
looked up by code. If no handler is registered for the code, it is looked up
|
||||
by its class hierarchy; the most specific handler is chosen. If no handler is
|
||||
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||
generic message about their code, while other exceptions are converted to a
|
||||
generic "500 Internal Server Error".
|
||||
generic 500 Internal Server Error.
|
||||
|
||||
For example, if an instance of :exc:`ConnectionRefusedError` is raised,
|
||||
and a handler is registered for :exc:`ConnectionError` and
|
||||
:exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError`
|
||||
handler is called with the exception instance to generate the response.
|
||||
For example, if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
|
||||
is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`,
|
||||
the more specific :exc:`ConnectionRefusedError` handler is called with the
|
||||
exception instance to generate the response.
|
||||
|
||||
Handlers registered on the blueprint take precedence over those registered
|
||||
globally on the application, assuming a blueprint is handling the request that
|
||||
|
|
@ -172,352 +138,78 @@ raises the exception. However, the blueprint cannot handle 404 routing errors
|
|||
because the 404 occurs at the routing level before the blueprint can be
|
||||
determined.
|
||||
|
||||
.. versionchanged:: 0.11
|
||||
|
||||
Generic Exception Handlers
|
||||
``````````````````````````
|
||||
|
||||
It is possible to register error handlers for very generic base classes
|
||||
such as ``HTTPException`` or even ``Exception``. However, be aware that
|
||||
these will catch more than you might expect.
|
||||
|
||||
For example, an error handler for ``HTTPException`` might be useful for turning
|
||||
the default HTML errors pages into JSON. However, this
|
||||
handler will trigger for things you don't cause directly, such as 404
|
||||
and 405 errors during routing. Be sure to craft your handler carefully
|
||||
so you don't lose information about the HTTP error.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import json
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(HTTPException)
|
||||
def handle_exception(e):
|
||||
"""Return JSON instead of HTML for HTTP errors."""
|
||||
# start with the correct headers and status code from the error
|
||||
response = e.get_response()
|
||||
# replace the body with JSON
|
||||
response.data = json.dumps({
|
||||
"code": e.code,
|
||||
"name": e.name,
|
||||
"description": e.description,
|
||||
})
|
||||
response.content_type = "application/json"
|
||||
return response
|
||||
|
||||
An error handler for ``Exception`` might seem useful for changing how
|
||||
all errors, even unhandled ones, are presented to the user. However,
|
||||
this is similar to doing ``except Exception:`` in Python, it will
|
||||
capture *all* otherwise unhandled errors, including all HTTP status
|
||||
codes.
|
||||
|
||||
In most cases it will be safer to register handlers for more
|
||||
specific exceptions. Since ``HTTPException`` instances are valid WSGI
|
||||
responses, you could also pass them through directly.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
# pass through HTTP errors
|
||||
if isinstance(e, HTTPException):
|
||||
return e
|
||||
|
||||
# now you're handling non-HTTP exceptions only
|
||||
return render_template("500_generic.html", e=e), 500
|
||||
|
||||
Error handlers still respect the exception class hierarchy. If you
|
||||
register handlers for both ``HTTPException`` and ``Exception``, the
|
||||
``Exception`` handler will not handle ``HTTPException`` subclasses
|
||||
because the ``HTTPException`` handler is more specific.
|
||||
|
||||
|
||||
Unhandled Exceptions
|
||||
````````````````````
|
||||
|
||||
When there is no error handler registered for an exception, a 500
|
||||
Internal Server Error will be returned instead. See
|
||||
:meth:`flask.Flask.handle_exception` for information about this
|
||||
behavior.
|
||||
|
||||
If there is an error handler registered for ``InternalServerError``,
|
||||
this will be invoked. As of Flask 1.1.0, this error handler will always
|
||||
be passed an instance of ``InternalServerError``, not the original
|
||||
unhandled error.
|
||||
|
||||
The original error is available as ``e.original_exception``.
|
||||
|
||||
An error handler for "500 Internal Server Error" will be passed uncaught
|
||||
exceptions in addition to explicit 500 errors. In debug mode, a handler
|
||||
for "500 Internal Server Error" will not be used. Instead, the
|
||||
interactive debugger will be shown.
|
||||
|
||||
|
||||
Custom Error Pages
|
||||
------------------
|
||||
|
||||
Sometimes when building a Flask application, you might want to raise a
|
||||
:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that
|
||||
something is wrong with the request. Fortunately, Flask comes with a handy
|
||||
:func:`~flask.abort` function that aborts a request with a HTTP error from
|
||||
werkzeug as desired. It will also provide a plain black and white error page
|
||||
for you with a basic description, but nothing fancy.
|
||||
|
||||
Depending on the error code it is less or more likely for the user to
|
||||
actually see such an error.
|
||||
|
||||
Consider the code below, we might have a user profile route, and if the user
|
||||
fails to pass a username we can raise a "400 Bad Request". If the user passes a
|
||||
username and we can't find it, we raise a "404 Not Found".
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import abort, render_template, request
|
||||
|
||||
# a username needs to be supplied in the query args
|
||||
# a successful request would be like /profile?username=jack
|
||||
@app.route("/profile")
|
||||
def user_profile():
|
||||
username = request.arg.get("username")
|
||||
# if a username isn't supplied in the request, return a 400 bad request
|
||||
if username is None:
|
||||
abort(400)
|
||||
|
||||
user = get_user(username=username)
|
||||
# if a user can't be found by their username, return 404 not found
|
||||
if user is None:
|
||||
abort(404)
|
||||
|
||||
return render_template("profile.html", user=user)
|
||||
|
||||
Here is another example implementation for a "404 Page Not Found" exception:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# note that we set the 404 status explicitly
|
||||
return render_template('404.html'), 404
|
||||
|
||||
When using :doc:`/patterns/appfactories`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(404, page_not_found)
|
||||
return app
|
||||
|
||||
An example template might be this:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Further Examples
|
||||
````````````````
|
||||
|
||||
The above examples wouldn't actually be an improvement on the default
|
||||
exception pages. We can create a custom 500.html template like this:
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Internal Server Error{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>Oops... we seem to have made a mistake, sorry!</p>
|
||||
<p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>
|
||||
{% endblock %}
|
||||
|
||||
It can be implemented by rendering the template on "500 Internal Server Error":
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
# note that we set the 500 status explicitly
|
||||
return render_template('500.html'), 500
|
||||
|
||||
When using :doc:`/patterns/appfactories`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def internal_server_error(e):
|
||||
return render_template('500.html'), 500
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(500, internal_server_error)
|
||||
return app
|
||||
|
||||
When using :doc:`/blueprints`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import Blueprint
|
||||
|
||||
blog = Blueprint('blog', __name__)
|
||||
|
||||
# as a decorator
|
||||
@blog.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
return render_template('500.html'), 500
|
||||
|
||||
# or with register_error_handler
|
||||
blog.register_error_handler(500, internal_server_error)
|
||||
|
||||
|
||||
Blueprint Error Handlers
|
||||
------------------------
|
||||
|
||||
In :doc:`/blueprints`, most error handlers will work as expected.
|
||||
However, there is a caveat concerning handlers for 404 and 405
|
||||
exceptions. These error handlers are only invoked from an appropriate
|
||||
``raise`` statement or a call to ``abort`` in another of the blueprint's
|
||||
view functions; they are not invoked by, e.g., an invalid URL access.
|
||||
|
||||
This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint error
|
||||
handler it should run if given an invalid URL. If you would like to
|
||||
execute different handling strategies for these errors based on URL
|
||||
prefixes, they may be defined at the application level using the
|
||||
``request`` proxy object.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import jsonify, render_template
|
||||
|
||||
# at the application level
|
||||
# not the blueprint level
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# if a request is in our blog URL space
|
||||
if request.path.startswith('/blog/'):
|
||||
# we return a custom blog 404 page
|
||||
return render_template("blog/404.html"), 404
|
||||
else:
|
||||
# otherwise we return our generic site-wide 404 page
|
||||
return render_template("404.html"), 404
|
||||
|
||||
@app.errorhandler(405)
|
||||
def method_not_allowed(e):
|
||||
# if a request has the wrong method to our API
|
||||
if request.path.startswith('/api/'):
|
||||
# we return a json saying so
|
||||
return jsonify(message="Method Not Allowed"), 405
|
||||
else:
|
||||
# otherwise we return a generic site-wide 405 page
|
||||
return render_template("405.html"), 405
|
||||
|
||||
|
||||
Returning API Errors as JSON
|
||||
----------------------------
|
||||
|
||||
When building APIs in Flask, some developers realise that the built-in
|
||||
exceptions are not expressive enough for APIs and that the content type of
|
||||
:mimetype:`text/html` they are emitting is not very useful for API consumers.
|
||||
|
||||
Using the same techniques as above and :func:`~flask.json.jsonify` we can return JSON
|
||||
responses to API errors. :func:`~flask.abort` is called
|
||||
with a ``description`` parameter. The error handler will
|
||||
use that as the JSON error message, and set the status code to 404.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import abort, jsonify
|
||||
|
||||
@app.errorhandler(404)
|
||||
def resource_not_found(e):
|
||||
return jsonify(error=str(e)), 404
|
||||
|
||||
@app.route("/cheese")
|
||||
def get_one_cheese():
|
||||
resource = get_resource()
|
||||
|
||||
if resource is None:
|
||||
abort(404, description="Resource not found")
|
||||
|
||||
return jsonify(resource)
|
||||
|
||||
We can also create custom exception classes. For instance, we can
|
||||
introduce a new custom exception for an API that can take a proper human readable message,
|
||||
a status code for the error and some optional payload to give more context
|
||||
for the error.
|
||||
|
||||
This is a simple example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from flask import jsonify, request
|
||||
|
||||
class InvalidAPIUsage(Exception):
|
||||
status_code = 400
|
||||
|
||||
def __init__(self, message, status_code=None, payload=None):
|
||||
super().__init__()
|
||||
self.message = message
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
self.payload = payload
|
||||
|
||||
def to_dict(self):
|
||||
rv = dict(self.payload or ())
|
||||
rv['message'] = self.message
|
||||
return rv
|
||||
|
||||
@app.errorhandler(InvalidAPIUsage)
|
||||
def invalid_api_usage(e):
|
||||
return jsonify(e.to_dict()), e.status_code
|
||||
|
||||
# an API app route for getting user information
|
||||
# a correct request might be /api/user?user_id=420
|
||||
@app.route("/api/user")
|
||||
def user_api(user_id):
|
||||
user_id = request.arg.get("user_id")
|
||||
if not user_id:
|
||||
raise InvalidAPIUsage("No user id provided!")
|
||||
|
||||
user = get_user(user_id=user_id)
|
||||
if not user:
|
||||
raise InvalidAPIUsage("No such user!", status_code=404)
|
||||
|
||||
return jsonify(user.to_dict())
|
||||
|
||||
A view can now raise that exception with an error message. Additionally
|
||||
some extra payload can be provided as a dictionary through the `payload`
|
||||
parameter.
|
||||
|
||||
Handlers are prioritized by specificity of the exception classes they are
|
||||
registered for instead of the order they are registered in.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
See :doc:`/logging` for information about how to log exceptions, such as
|
||||
by emailing them to admins.
|
||||
See :ref:`logging` for information on how to log exceptions, such as by
|
||||
emailing them to admins.
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
Debugging Application Errors
|
||||
============================
|
||||
|
||||
See :doc:`/debugging` for information about how to debug errors in
|
||||
development and production.
|
||||
For production applications, configure your application with logging and
|
||||
notifications as described in :ref:`application-errors`. This section provides
|
||||
pointers when debugging deployment configuration and digging deeper with a
|
||||
full-featured Python debugger.
|
||||
|
||||
|
||||
When in Doubt, Run Manually
|
||||
---------------------------
|
||||
|
||||
Having problems getting your application configured for production? If you
|
||||
have shell access to your host, verify that you can run your application
|
||||
manually from the shell in the deployment environment. Be sure to run under
|
||||
the same user account as the configured deployment to troubleshoot permission
|
||||
issues. You can use Flask's builtin development server with `debug=True` on
|
||||
your production host, which is helpful in catching configuration issues, but
|
||||
**be sure to do this temporarily in a controlled environment.** Do not run in
|
||||
production with `debug=True`.
|
||||
|
||||
|
||||
.. _working-with-debuggers:
|
||||
|
||||
Working with Debuggers
|
||||
----------------------
|
||||
|
||||
To dig deeper, possibly to trace code execution, Flask provides a debugger out
|
||||
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:
|
||||
|
||||
* ``debug`` - whether to enable debug mode and catch exceptions
|
||||
* ``use_debugger`` - whether to use the internal Flask debugger
|
||||
* ``use_reloader`` - whether to reload and fork the process on exception
|
||||
|
||||
``debug`` must be True (i.e., exceptions must be caught) in order for the other
|
||||
two options to have any value.
|
||||
|
||||
If you're using Aptana/Eclipse for debugging you'll need to set both
|
||||
``use_debugger`` and ``use_reloader`` to False.
|
||||
|
||||
A possible useful pattern for configuration is to set the following in your
|
||||
config.yaml (change the block as appropriate for your application, of course)::
|
||||
|
||||
FLASK:
|
||||
DEBUG: True
|
||||
DEBUG_WITH_APTANA: True
|
||||
|
||||
Then in your application's entry-point (main.py), you could have something like::
|
||||
|
||||
if __name__ == "__main__":
|
||||
# To allow aptana to receive errors, set use_debugger=False
|
||||
app = create_app(config="config.yaml")
|
||||
|
||||
if app.debug: use_debugger = True
|
||||
try:
|
||||
# Disable Flask's debugger if external debugger is requested
|
||||
use_debugger = not(app.config.get('DEBUG_WITH_APTANA'))
|
||||
except:
|
||||
pass
|
||||
app.run(use_debugger=use_debugger, debug=app.debug,
|
||||
use_reloader=use_debugger, host='0.0.0.0')
|
||||
|
|
|
|||
|
|
@ -1,305 +1,345 @@
|
|||
.. _extension-dev:
|
||||
|
||||
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
|
||||
application. While `PyPI`_ contains many Flask extensions, you may not
|
||||
find one that fits your need. If this is the case, you can create your
|
||||
own, and publish it for others to use as well.
|
||||
If you want to create your own Flask extension for something that does not
|
||||
exist yet, this guide to extension development will help you get your
|
||||
extension running in no time and to feel like users would expect your
|
||||
extension to behave.
|
||||
|
||||
This guide will show how to create a Flask extension, and some of the
|
||||
common patterns and requirements involved. Since extensions can do
|
||||
anything, this guide won't be able to cover every possibility.
|
||||
.. _Flask Extension Registry: http://flask.pocoo.org/extensions/
|
||||
|
||||
The best ways to learn about extensions are to look at how other
|
||||
extensions you use are written, and discuss with others. Discuss your
|
||||
design ideas with others on our `Discord Chat`_ or
|
||||
`GitHub Discussions`_.
|
||||
Anatomy of an Extension
|
||||
-----------------------
|
||||
|
||||
The best extensions share common patterns, so that anyone familiar with
|
||||
using one extension won't feel completely lost with another. This can
|
||||
only work if collaboration happens early.
|
||||
Extensions are all located in a package called ``flask_something``
|
||||
where "something" is the name of the library you want to bridge. So for
|
||||
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.
|
||||
|
||||
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
|
||||
in order to be listed 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
|
||||
suffix. If it wraps another library, it should include the library name
|
||||
as well. This makes it easy to search for extensions, and makes their
|
||||
purpose clearer.
|
||||
That's a lot of code but you can really just copy/paste that from existing
|
||||
extensions and adapt.
|
||||
|
||||
A general Python packaging recommendation is that the install name from
|
||||
the package index and the name used in ``import`` statements should be
|
||||
related. The import name is lowercase, with words separated by
|
||||
underscores (``_``). The install name is either lower case or title
|
||||
case, with words separated by dashes (``-``). If it wraps another
|
||||
library, prefer using the same case as that library's name.
|
||||
flask_sqlite3.py
|
||||
````````````````
|
||||
|
||||
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``
|
||||
- ``flask-name-lower`` imported as ``flask_name_lower``
|
||||
- ``Flask-ComboName`` imported as ``flask_comboname``
|
||||
- ``Name-Flask`` imported as ``name_flask``
|
||||
Initializing Extensions
|
||||
-----------------------
|
||||
|
||||
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.
|
||||
|
||||
When designing your classes, it's important to make them easily reusable
|
||||
at the module level. This means the object itself must not under any
|
||||
circumstances store any application specific state and must be shareable
|
||||
between different applications.
|
||||
|
||||
The Extension Code
|
||||
------------------
|
||||
|
||||
Here's the contents of the `flask_sqlite3.py` for copy/paste::
|
||||
|
||||
import sqlite3
|
||||
from flask import current_app, _app_ctx_stack
|
||||
|
||||
|
||||
The Extension Class and Initialization
|
||||
--------------------------------------
|
||||
|
||||
All extensions will need some entry point that initializes the
|
||||
extension with the application. The most common pattern is to create a
|
||||
class that represents the extension's configuration and behavior, with
|
||||
an ``init_app`` method to apply the extension instance to the given
|
||||
application instance.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class HelloExtension:
|
||||
class SQLite3(object):
|
||||
def __init__(self, app=None):
|
||||
self.app = app
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
def init_app(self, app):
|
||||
app.before_request(...)
|
||||
app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||
app.teardown_appcontext(self.teardown)
|
||||
|
||||
It is important that the app is not stored on the extension, don't do
|
||||
``self.app = app``. The only time the extension should have direct
|
||||
access to an app is during ``init_app``, otherwise it should use
|
||||
:data:`.current_app`.
|
||||
def connect(self):
|
||||
return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])
|
||||
|
||||
This allows the extension to support the application factory pattern,
|
||||
avoids circular import issues when importing the extension instance
|
||||
elsewhere in a user's code, and makes testing with different
|
||||
configurations easier.
|
||||
def teardown(self, exception):
|
||||
ctx = _app_ctx_stack.top
|
||||
if hasattr(ctx, 'sqlite3_db'):
|
||||
ctx.sqlite3_db.close()
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
hello = HelloExtension()
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
hello.init_app(app)
|
||||
return app
|
||||
|
||||
Above, the ``hello`` extension instance exists independently of the
|
||||
application. This means that other modules in a user's project can do
|
||||
``from project import hello`` and use the extension in blueprints before
|
||||
the app exists.
|
||||
|
||||
The :attr:`Flask.extensions` dict can be used to store a reference to
|
||||
the extension on the application, or some other state specific to the
|
||||
application. Be aware that this is a single namespace, so use a name
|
||||
unique to your extension, such as the extension's name without the
|
||||
"flask" prefix.
|
||||
@property
|
||||
def connection(self):
|
||||
ctx = _app_ctx_stack.top
|
||||
if ctx is not None:
|
||||
if not hasattr(ctx, 'sqlite3_db'):
|
||||
ctx.sqlite3_db = self.connect()
|
||||
return ctx.sqlite3_db
|
||||
|
||||
|
||||
Adding Behavior
|
||||
---------------
|
||||
So here's what these lines of code do:
|
||||
|
||||
There are many ways that an extension can add behavior. Any setup
|
||||
methods that are available on the :class:`Flask` object can be used
|
||||
during an extension's ``init_app`` method.
|
||||
1. The ``__init__`` method takes an optional app object and, if supplied, will
|
||||
call ``init_app``.
|
||||
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.
|
||||
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
|
||||
some data or a connection at the beginning of each request, then
|
||||
:meth:`~Flask.teardown_request` to clean it up at the end. This can be
|
||||
stored on :data:`.g`, discussed more below.
|
||||
Note here that we're attaching our database connection to the top
|
||||
application context via ``_app_ctx_stack.top``. Extensions should use
|
||||
the top context for storing their own information with a sufficiently
|
||||
complex name.
|
||||
|
||||
A more lazy approach is to provide a method that initializes and caches
|
||||
the data or connection. For example, a ``ext.get_db`` method could
|
||||
create a database connection the first time it's called, so that a view
|
||||
that doesn't use the database doesn't create a connection.
|
||||
So why did we decide on a class-based approach here? Because using our
|
||||
extension looks something like this::
|
||||
|
||||
Besides doing something before and after every view, your extension
|
||||
might want to add some specific views as well. In this case, you could
|
||||
define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint`
|
||||
during ``init_app`` to add the blueprint to the app.
|
||||
from flask import Flask
|
||||
from flask_sqlite3 import SQLite3
|
||||
|
||||
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
|
||||
extension. You should consider what parts of your extension fall into
|
||||
each one.
|
||||
@app.route('/')
|
||||
def show_all():
|
||||
cur = db.connection.cursor()
|
||||
cur.execute(...)
|
||||
|
||||
- Configuration per application instance, through ``app.config``
|
||||
values. This is configuration that could reasonably change for each
|
||||
deployment of an application. A common example is a URL to an
|
||||
external resource, such as a database. Configuration keys should
|
||||
start with the extension's name so that they don't interfere with
|
||||
other extensions.
|
||||
- Configuration per extension instance, through ``__init__``
|
||||
arguments. This configuration usually affects how the extension
|
||||
is used, such that it wouldn't make sense to change it per
|
||||
deployment.
|
||||
- Configuration per extension instance, through instance attributes
|
||||
and decorator methods. It might be more ergonomic to assign to
|
||||
``ext.value``, or use a ``@ext.register`` decorator to register a
|
||||
function, after the extension instance has been created.
|
||||
- Global configuration through class attributes. Changing a class
|
||||
attribute like ``Ext.connection_class`` can customize default
|
||||
behavior without making a subclass. This could be combined
|
||||
per-extension configuration to override defaults.
|
||||
- Subclassing and overriding methods and attributes. Making the API of
|
||||
the extension itself something that can be overridden provides a
|
||||
very powerful tool for advanced customization.
|
||||
Likewise if you are outside of a request you can use the database by
|
||||
pushing an app context::
|
||||
|
||||
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
|
||||
extension, based on what you need and what you want to support.
|
||||
At the end of the ``with`` block the teardown handles will be executed
|
||||
automatically.
|
||||
|
||||
Configuration should not be changed after the application setup phase is
|
||||
complete and the server begins handling requests. Configuration is
|
||||
global, any changes to it are not guaranteed to be visible to other
|
||||
workers.
|
||||
Additionally, the ``init_app`` method is used to support the factory pattern
|
||||
for creating apps::
|
||||
|
||||
|
||||
Data During a Request
|
||||
---------------------
|
||||
|
||||
When writing a Flask application, the :data:`~flask.g` object is used to
|
||||
store information during a request. For example the
|
||||
:doc:`tutorial <tutorial/database>` stores a connection to a SQLite
|
||||
database as ``g.db``. Extensions can also use this, with some care.
|
||||
Since ``g`` is a single global namespace, extensions must use unique
|
||||
names that won't collide with user data. For example, use the extension
|
||||
name as a prefix, or as a namespace.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# an internal prefix with the extension name
|
||||
g._hello_user_id = 2
|
||||
|
||||
# or an internal prefix as a namespace
|
||||
from types import SimpleNamespace
|
||||
g._hello = SimpleNamespace()
|
||||
g._hello.user_id = 2
|
||||
|
||||
The data in ``g`` lasts for an application context. An application context is
|
||||
active during a request, CLI command, or ``with app.app_context()`` block. If
|
||||
you're storing something that should be closed, use
|
||||
:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed when the
|
||||
app context ends. If it should only be valid during a request, or would not be
|
||||
used in the CLI outside a request, use :meth:`~flask.Flask.teardown_request`.
|
||||
|
||||
|
||||
Views and Models
|
||||
----------------
|
||||
|
||||
Your extension views might want to interact with specific models in your
|
||||
database, or some other extension or data connected to your application.
|
||||
For example, let's consider a ``Flask-SimpleBlog`` extension that works
|
||||
with Flask-SQLAlchemy to provide a ``Post`` model and views to write
|
||||
and read posts.
|
||||
|
||||
The ``Post`` model needs to subclass the Flask-SQLAlchemy ``db.Model``
|
||||
object, but that's only available once you've created an instance of
|
||||
that extension, not when your extension is defining its views. So how
|
||||
can the view code, defined before the model exists, access the model?
|
||||
|
||||
One method could be to use :doc:`views`. During ``__init__``, create
|
||||
the model, then create the views by passing the model to the view
|
||||
class's :meth:`~views.View.as_view` method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class PostAPI(MethodView):
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
|
||||
def get(self, id):
|
||||
post = self.model.query.get(id)
|
||||
return jsonify(post.to_json())
|
||||
|
||||
class BlogExtension:
|
||||
def __init__(self, db):
|
||||
class Post(db.Model):
|
||||
id = db.Column(primary_key=True)
|
||||
title = db.Column(db.String, nullable=False)
|
||||
|
||||
self.post_model = Post
|
||||
|
||||
def init_app(self, app):
|
||||
api_view = PostAPI.as_view(model=self.post_model)
|
||||
|
||||
db = SQLAlchemy()
|
||||
blog = BlogExtension(db)
|
||||
db = Sqlite3()
|
||||
# Then later on.
|
||||
app = create_app('the-config.cfg')
|
||||
db.init_app(app)
|
||||
blog.init_app(app)
|
||||
|
||||
Another technique could be to use an attribute on the extension, such as
|
||||
``self.post_model`` from above. Add the extension to ``app.extensions``
|
||||
in ``init_app``, then access
|
||||
``current_app.extensions["simple_blog"].post_model`` from views.
|
||||
Keep in mind that supporting this factory pattern for creating apps is required
|
||||
for approved flask extensions (described below).
|
||||
|
||||
You may also want to provide base classes so that users can provide
|
||||
their own ``Post`` model that conforms to the API your extension
|
||||
expects. So they could implement ``class Post(blog.BasePost)``, then
|
||||
set it as ``blog.post_model``.
|
||||
.. admonition:: Note on ``init_app``
|
||||
|
||||
As you can see, this can get a bit complex. Unfortunately, there's no
|
||||
perfect solution here, only different strategies and tradeoffs depending
|
||||
on your needs and how much customization you want to offer. Luckily,
|
||||
this sort of resource dependency is not a common need for most
|
||||
extensions. Remember, if you need help with design, ask on our
|
||||
`Discord Chat`_ or `GitHub Discussions`_.
|
||||
As you noticed, ``init_app`` does not assign ``app`` to ``self``. This
|
||||
is intentional! Class based Flask extensions must only store the
|
||||
application on the object when the application was passed to the
|
||||
constructor. This tells the extension: I am not interested in using
|
||||
multiple applications.
|
||||
|
||||
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
|
||||
Flask maintainers evaluated the quality, support, and compatibility of
|
||||
the extensions before listing them. While the list became too difficult
|
||||
to maintain over time, the guidelines are still relevant to all
|
||||
extensions maintained and developed today, as they help the Flask
|
||||
ecosystem remain consistent and compatible.
|
||||
In the example above, before every request, a ``sqlite3_db`` variable is
|
||||
assigned to ``_app_ctx_stack.top``. In a view function, this variable is
|
||||
accessible using the ``connection`` property of ``SQLite3``. During the
|
||||
teardown of a request, the ``sqlite3_db`` connection is closed. By using
|
||||
this pattern, the *same* connection to the sqlite3 database is accessible
|
||||
to anything that needs it for the duration of the request.
|
||||
|
||||
1. An extension requires a maintainer. In the event an extension author
|
||||
would like to move beyond the project, the project should find a new
|
||||
maintainer and transfer access to the repository, documentation,
|
||||
PyPI, and any other services. The `Pallets-Eco`_ organization on
|
||||
GitHub allows for community maintenance with oversight from the
|
||||
Pallets maintainers.
|
||||
2. The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*.
|
||||
It must provide exactly one package or module named
|
||||
``flask_extension_name``.
|
||||
3. The extension must use an open source license. The Python web
|
||||
ecosystem tends to prefer BSD or MIT. It must be open source and
|
||||
publicly available.
|
||||
4. The extension's API must have the following characteristics:
|
||||
|
||||
- It must support multiple applications running in the same Python
|
||||
process. Use ``current_app`` instead of ``self.app``, store
|
||||
configuration and state per application instance.
|
||||
- It must be possible to use the factory pattern for creating
|
||||
applications. Use the ``ext.init_app()`` pattern.
|
||||
Learn from Others
|
||||
-----------------
|
||||
|
||||
5. From a clone of the repository, an extension with its dependencies
|
||||
must be installable in editable mode with ``pip install -e .``.
|
||||
6. It must ship tests that can be invoked with a common tool like
|
||||
``tox -e py``, ``nox -s test`` or ``pytest``. If not using ``tox``,
|
||||
the test dependencies should be specified in a requirements file.
|
||||
The tests must be part of the sdist distribution.
|
||||
7. A link to the documentation or project website must be in the PyPI
|
||||
metadata or the readme. The documentation should use the Flask theme
|
||||
from the `Official Pallets Themes`_.
|
||||
8. The extension's dependencies should not use upper bounds or assume
|
||||
any particular version scheme, but should use lower bounds to
|
||||
indicate minimum compatibility support. For example,
|
||||
``sqlalchemy>=1.4``.
|
||||
9. Indicate the versions of Python supported using ``python_requires=">=version"``.
|
||||
Flask and Pallets policy is to support all Python versions that are not
|
||||
within six months of end of life (EOL). See Python's `EOL calendar`_ for
|
||||
timing.
|
||||
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
|
||||
generates useful feedback on what people might want from an extension, but
|
||||
also avoids having multiple developers working in isolation on pretty much the
|
||||
same problem.
|
||||
|
||||
.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask
|
||||
.. _Discord Chat: https://discord.gg/pallets
|
||||
.. _GitHub Discussions: https://github.com/pallets/flask/discussions
|
||||
.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/
|
||||
.. _Pallets-Eco: https://github.com/pallets-eco
|
||||
.. _EOL calendar: https://devguide.python.org/versions/
|
||||
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 documentation must use the ``flask`` theme from the `Official
|
||||
Pallets Themes`_.
|
||||
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 3.4 and newer and 2.7.
|
||||
|
||||
|
||||
.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/
|
||||
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
||||
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
||||
.. _Official Pallets Themes: https://pypi.org/project/pallets-sphinx-themes/
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
.. _extensions:
|
||||
|
||||
Extensions
|
||||
==========
|
||||
|
||||
Extensions are extra packages that add functionality to a Flask
|
||||
application. For example, an extension might add support for sending
|
||||
email or connecting to a database. Some extensions add entire new
|
||||
frameworks to help build certain types of applications, like a REST API.
|
||||
frameworks to help build certain types of applications, like a ReST API.
|
||||
|
||||
|
||||
Finding Extensions
|
||||
------------------
|
||||
|
||||
Flask extensions are usually named "Flask-Foo" or "Foo-Flask". You can
|
||||
search PyPI for packages tagged with `Framework :: Flask <pypi_>`_.
|
||||
Flask extensions are usually named "Flask-Foo" or "Foo-Flask". Many
|
||||
extensions are listed in the `Extension Registry`_, which can be updated
|
||||
by extension developers. You can also search PyPI for packages tagged
|
||||
with `Framework :: Flask <pypi_>`_.
|
||||
|
||||
|
||||
Using Extensions
|
||||
|
|
@ -21,7 +25,7 @@ Consult each extension's documentation for installation, configuration,
|
|||
and usage instructions. Generally, extensions pull their own
|
||||
configuration from :attr:`app.config <flask.Flask.config>` and are
|
||||
passed an application instance during initialization. For example,
|
||||
an extension called "Flask-Foo" might be used like this::
|
||||
an extension caled "Flask-Foo" might be used like this::
|
||||
|
||||
from flask_foo import Foo
|
||||
|
||||
|
|
@ -39,10 +43,11 @@ an extension called "Flask-Foo" might be used like this::
|
|||
Building Extensions
|
||||
-------------------
|
||||
|
||||
While `PyPI <pypi_>`_ contains many Flask extensions, you may not find
|
||||
an extension that fits your need. If this is the case, you can create
|
||||
your own, and publish it for others to use as well. Read
|
||||
:doc:`extensiondev` to develop your own Flask extension.
|
||||
While the `Extension Registry`_ contains many Flask extensions, you may
|
||||
not find an extension that fits your need. If this is the case, you can
|
||||
create your own. Read :ref:`extension-dev` to develop your own Flask
|
||||
extension.
|
||||
|
||||
|
||||
.. _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.
|
||||
|
|
@ -1,89 +1,26 @@
|
|||
.. rst-class:: hide-header
|
||||
:orphan:
|
||||
|
||||
Welcome to Flask
|
||||
================
|
||||
|
||||
.. image:: _static/flask-name.svg
|
||||
:align: center
|
||||
:height: 200px
|
||||
.. image:: _static/logo-full.png
|
||||
:alt: Flask: web development, one drop at a time
|
||||
:align: right
|
||||
|
||||
Welcome to Flask's documentation. Flask is a lightweight WSGI web application framework.
|
||||
It is designed to make getting started quick and easy, with the ability to scale up to
|
||||
complex applications.
|
||||
|
||||
Get started with :doc:`installation`
|
||||
and then get an overview with the :doc:`quickstart`. There is also a
|
||||
more detailed :doc:`tutorial/index` that shows how to create a small but
|
||||
Welcome to Flask's documentation. Get started with :ref:`installation`
|
||||
and then get an overview with the :ref:`quickstart`. There is also a
|
||||
more detailed :ref:`tutorial` that shows how to create a small but
|
||||
complete application with Flask. Common patterns are described in the
|
||||
:doc:`patterns/index` section. The rest of the docs describe each
|
||||
component of Flask in detail, with a full reference in the :doc:`api`
|
||||
section.
|
||||
:ref:`patterns` section. The rest of the docs describe each component of
|
||||
Flask in detail, with a full reference in the :ref:`api` section.
|
||||
|
||||
Flask depends on the `Werkzeug`_ WSGI toolkit, the `Jinja`_ template engine, and the
|
||||
`Click`_ CLI toolkit. Be sure to check their documentation as well as Flask's when
|
||||
looking for information.
|
||||
Flask depends on the `Jinja`_ template engine and the `Werkzeug`_ WSGI
|
||||
toolkit. The documentation for these libraries can be found at:
|
||||
|
||||
.. _Werkzeug: https://werkzeug.palletsprojects.com
|
||||
.. _Jinja: https://jinja.palletsprojects.com
|
||||
.. _Click: https://click.palletsprojects.com
|
||||
- `Jinja documentation <http://jinja.pocoo.org/docs>`_
|
||||
- `Werkzeug documentation <http://werkzeug.pocoo.org/docs>`_
|
||||
|
||||
.. _Jinja: https://www.palletsprojects.com/p/jinja/
|
||||
.. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/
|
||||
|
||||
User's Guide
|
||||
------------
|
||||
|
||||
Flask provides configuration and conventions, with sensible defaults, to get started.
|
||||
This section of the documentation explains the different parts of the Flask framework
|
||||
and how they can be used, customized, and extended. Beyond Flask itself, look for
|
||||
community-maintained extensions to add even more functionality.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
installation
|
||||
quickstart
|
||||
tutorial/index
|
||||
templating
|
||||
testing
|
||||
errorhandling
|
||||
debugging
|
||||
logging
|
||||
config
|
||||
signals
|
||||
views
|
||||
lifecycle
|
||||
appcontext
|
||||
blueprints
|
||||
extensions
|
||||
cli
|
||||
server
|
||||
shell
|
||||
patterns/index
|
||||
web-security
|
||||
deploying/index
|
||||
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
|
||||
.. include:: contents.rst.inc
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
.. _installation:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
|
||||
Python Version
|
||||
--------------
|
||||
|
||||
We recommend using the latest version of Python. Flask supports Python 3.10 and newer.
|
||||
|
||||
We recommend using the latest version of Python 3. Flask supports Python 3.4
|
||||
and newer, Python 2.7, and PyPy.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
|
@ -23,15 +24,12 @@ These distributions will be installed automatically when installing Flask.
|
|||
to protect Flask's session cookie.
|
||||
* `Click`_ is a framework for writing command line applications. It provides
|
||||
the ``flask`` command and allows adding custom management commands.
|
||||
* `Blinker`_ provides support for :doc:`signals`.
|
||||
|
||||
.. _Werkzeug: https://palletsprojects.com/p/werkzeug/
|
||||
.. _Jinja: https://palletsprojects.com/p/jinja/
|
||||
.. _MarkupSafe: https://palletsprojects.com/p/markupsafe/
|
||||
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
||||
.. _Click: https://palletsprojects.com/p/click/
|
||||
.. _Blinker: https://blinker.readthedocs.io/
|
||||
|
||||
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||
.. _Jinja: http://jinja.pocoo.org/
|
||||
.. _MarkupSafe: https://pypi.org/project/MarkupSafe/
|
||||
.. _ItsDangerous: https://pythonhosted.org/itsdangerous/
|
||||
.. _Click: http://click.pocoo.org/
|
||||
|
||||
Optional dependencies
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -39,26 +37,20 @@ Optional dependencies
|
|||
These distributions will not be installed automatically. Flask will detect and
|
||||
use them if you install them.
|
||||
|
||||
* `Blinker`_ provides support for :ref:`signals`.
|
||||
* `SimpleJSON`_ is a fast JSON implementation that is compatible with
|
||||
Python's ``json`` module. It is preferred for JSON operations if it is
|
||||
installed.
|
||||
* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask``
|
||||
commands.
|
||||
* `Watchdog`_ provides a faster, more efficient reloader for the development
|
||||
server.
|
||||
|
||||
.. _Blinker: https://pythonhosted.org/blinker/
|
||||
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||
.. _watchdog: https://pythonhosted.org/watchdog/
|
||||
|
||||
|
||||
greenlet
|
||||
~~~~~~~~
|
||||
|
||||
You may choose to use :doc:`/gevent` with your application. In this case,
|
||||
greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is required.
|
||||
|
||||
These are not minimum supported versions, they only indicate the first
|
||||
versions that added necessary features. You should use the latest
|
||||
versions of each.
|
||||
|
||||
|
||||
Virtual environments
|
||||
--------------------
|
||||
|
||||
|
|
@ -74,35 +66,43 @@ Virtual environments are independent groups of Python libraries, one for each
|
|||
project. Packages installed for one project will not affect other projects or
|
||||
the operating system's packages.
|
||||
|
||||
Python comes bundled with the :mod:`venv` module to create virtual
|
||||
environments.
|
||||
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
||||
environments. If you're using a modern version of Python, you can continue on
|
||||
to the next section.
|
||||
|
||||
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
||||
|
||||
.. _install-create-env:
|
||||
|
||||
Create an environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Create a project folder and a :file:`.venv` folder within:
|
||||
Create a project folder and a :file:`venv` folder within:
|
||||
|
||||
.. tabs::
|
||||
.. code-block:: sh
|
||||
|
||||
.. group-tab:: macOS/Linux
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
python3 -m venv venv
|
||||
|
||||
.. code-block:: text
|
||||
On Windows:
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
$ python3 -m venv .venv
|
||||
.. code-block:: bat
|
||||
|
||||
.. group-tab:: Windows
|
||||
py -3 -m venv venv
|
||||
|
||||
.. code-block:: text
|
||||
If you needed to install virtualenv because you are on an older version of
|
||||
Python, use the following command instead:
|
||||
|
||||
> mkdir myproject
|
||||
> cd myproject
|
||||
> py -3 -m venv .venv
|
||||
.. code-block:: sh
|
||||
|
||||
virtualenv venv
|
||||
|
||||
On Windows:
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
\Python27\Scripts\virtualenv.exe venv
|
||||
|
||||
.. _install-activate-env:
|
||||
|
||||
|
|
@ -111,33 +111,73 @@ Activate the environment
|
|||
|
||||
Before you work on your project, activate the corresponding environment:
|
||||
|
||||
.. tabs::
|
||||
.. code-block:: sh
|
||||
|
||||
.. group-tab:: macOS/Linux
|
||||
. venv/bin/activate
|
||||
|
||||
.. code-block:: text
|
||||
On Windows:
|
||||
|
||||
$ . .venv/bin/activate
|
||||
.. code-block:: bat
|
||||
|
||||
.. group-tab:: Windows
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
> .venv\Scripts\activate
|
||||
|
||||
Your shell prompt will change to show the name of the activated
|
||||
environment.
|
||||
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:
|
||||
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
|
||||
:doc:`Documentation Overview </index>`.
|
||||
Living on the edge
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to work with the latest Flask code before it's released, install or
|
||||
update the code from the master branch:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install -U https://github.com/pallets/flask/archive/master.tar.gz
|
||||
|
||||
.. _install-install-virtualenv:
|
||||
|
||||
Install virtualenv
|
||||
------------------
|
||||
|
||||
If you are using Python 2, the venv module is not available. Instead,
|
||||
install `virtualenv`_.
|
||||
|
||||
On Linux, virtualenv is provided by your package manager:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# Debian, Ubuntu
|
||||
sudo apt-get install python-virtualenv
|
||||
|
||||
# CentOS, Fedora
|
||||
sudo yum install python-virtualenv
|
||||
|
||||
# Arch
|
||||
sudo pacman -S python-virtualenv
|
||||
|
||||
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sudo python2 Downloads/get-pip.py
|
||||
sudo python2 -m pip install virtualenv
|
||||
|
||||
On Windows, as an administrator:
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
\Python27\python.exe Downloads\get-pip.py
|
||||
\Python27\python.exe -m pip install virtualenv
|
||||
|
||||
Now you can continue to :ref:`install-create-env`.
|
||||
|
||||
.. _virtualenv: https://virtualenv.pypa.io/
|
||||
.. _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
|
||||
:language: text
|
||||
Flask is licensed under a three clause BSD License. It basically means:
|
||||
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.
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
.. _logging:
|
||||
|
||||
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
|
||||
Flask uses standard Python :mod:`logging`. All Flask-related messages are
|
||||
logged under the ``'flask'`` logger namespace.
|
||||
:meth:`Flask.logger <flask.Flask.logger>` returns the logger named
|
||||
``'flask.app'``, and can be used to log messages for your application. ::
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login():
|
||||
|
|
@ -20,9 +20,6 @@ logger can also be used to log your own messages.
|
|||
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
|
||||
-------------------
|
||||
|
|
@ -120,18 +117,13 @@ 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 import 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
|
||||
|
||||
record.url = request.url
|
||||
record.remote_addr = request.remote_addr
|
||||
return super().format(record)
|
||||
|
||||
formatter = RequestFormatter(
|
||||
|
|
@ -159,7 +151,7 @@ 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),
|
||||
app.logger,
|
||||
logging.getLogger('sqlalchemy'),
|
||||
logging.getLogger('other_package'),
|
||||
):
|
||||
|
|
|
|||
BIN
docs/logo.pdf
Normal file
|
|
@ -1,35 +1,36 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=Flask
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
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
|
||||
=======================
|
||||
|
||||
|
|
@ -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
|
||||
internally.
|
||||
|
||||
The fundamental difference from :doc:`packages` is that in this case you
|
||||
are running the same or different Flask applications that are entirely
|
||||
isolated from each other. They run different configurations and are
|
||||
dispatched on the WSGI level.
|
||||
The fundamental difference from the :ref:`module approach
|
||||
<larger-applications>` is that in this case you are running the same or
|
||||
different Flask applications that are entirely isolated from each other.
|
||||
They run different configurations and are dispatched on the WSGI level.
|
||||
|
||||
|
||||
Working with this Document
|
||||
--------------------------
|
||||
|
||||
Each of the techniques and examples below results in an ``application``
|
||||
object that can be run with any WSGI server. For development, use the
|
||||
``flask run`` command to start a development server. For production, see
|
||||
:doc:`/deploying/index`.
|
||||
Each of the techniques and examples below results in an ``application`` object
|
||||
that can be run with any WSGI server. For production, see :ref:`deployment`.
|
||||
For development, Werkzeug provides a builtin server for development available
|
||||
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 werkzeug.serving import run_simple
|
||||
|
||||
app = Flask(__name__)
|
||||
app.debug = True
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello World!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_simple('localhost', 5000, app,
|
||||
use_reloader=True, use_debugger=True, use_evalex=True)
|
||||
|
||||
|
||||
Combining Applications
|
||||
----------------------
|
||||
|
|
@ -44,16 +60,14 @@ are combined by the dispatcher middleware into a larger one that is
|
|||
dispatched based on prefix.
|
||||
|
||||
For example you could have your main application run on ``/`` and your
|
||||
backend interface on ``/backend``.
|
||||
backend interface on ``/backend``::
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||
from werkzeug.wsgi import DispatcherMiddleware
|
||||
from frontend_app import application as frontend
|
||||
from backend_app import application as backend
|
||||
|
||||
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
|
||||
really easy to implement. In order to develop your application to support
|
||||
creating new instances in functions have a look at the
|
||||
:doc:`appfactories` pattern.
|
||||
:ref:`app-factories` pattern.
|
||||
|
||||
A very common example would be creating applications per subdomain. For
|
||||
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
|
||||
write your own WSGI application that looks at the request that comes and
|
||||
delegates it to your Flask application. If that application does not
|
||||
exist yet, it is dynamically created and remembered.
|
||||
|
||||
.. code-block:: python
|
||||
exist yet, it is dynamically created and remembered::
|
||||
|
||||
from threading import Lock
|
||||
|
||||
class SubdomainDispatcher:
|
||||
class SubdomainDispatcher(object):
|
||||
|
||||
def __init__(self, domain, create_app):
|
||||
self.domain = domain
|
||||
|
|
@ -107,9 +119,7 @@ exist yet, it is dynamically created and remembered.
|
|||
return app(environ, start_response)
|
||||
|
||||
|
||||
This dispatcher can then be used like this:
|
||||
|
||||
.. code-block:: python
|
||||
This dispatcher can then be used like this::
|
||||
|
||||
from myapplication import create_app, get_user_for_subdomain
|
||||
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
|
||||
the ``Host`` header to figure out the subdomain one simply looks at the
|
||||
request path up to the first slash.
|
||||
|
||||
.. code-block:: python
|
||||
request path up to the first slash::
|
||||
|
||||
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):
|
||||
self.default_app = default_app
|
||||
|
|
@ -160,24 +168,15 @@ request path up to the first slash.
|
|||
return app
|
||||
|
||||
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:
|
||||
shift_path_info(environ)
|
||||
pop_path_info(environ)
|
||||
else:
|
||||
app = self.default_app
|
||||
return app(environ, start_response)
|
||||
|
||||
def _peek_path_info(environ):
|
||||
segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1)
|
||||
if segments:
|
||||
return segments[0]
|
||||
|
||||
return None
|
||||
|
||||
The big difference between this and the subdomain one is that this one
|
||||
falls back to another application if the creator function returns ``None``.
|
||||
|
||||
.. code-block:: python
|
||||
falls back to another application if the creator function returns ``None``::
|
||||
|
||||
from myapplication import create_app, default_app, get_user_for_prefix
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
.. _app-factories:
|
||||
|
||||
Application Factories
|
||||
=====================
|
||||
|
||||
If you are already using packages and blueprints for your application
|
||||
(:doc:`/blueprints`) there are a couple of really nice ways to further improve
|
||||
(: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 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.
|
||||
|
|
@ -58,7 +60,7 @@ Factories & Extensions
|
|||
It's preferable to create your extensions and app factories so that the
|
||||
extension object does not initially get bound to the application.
|
||||
|
||||
Using `Flask-SQLAlchemy <https://flask-sqlalchemy.palletsprojects.com/>`_,
|
||||
Using `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_,
|
||||
as an example, you should not do something along those lines::
|
||||
|
||||
def create_app(config_filename):
|
||||
|
|
@ -87,22 +89,19 @@ For more information about the design of extensions refer to :doc:`/extensiondev
|
|||
Using Applications
|
||||
------------------
|
||||
|
||||
To run such an application, you can use the :command:`flask` command:
|
||||
To run such an application, you can use the :command:`flask` command::
|
||||
|
||||
.. code-block:: text
|
||||
export FLASK_APP=myapp
|
||||
flask run
|
||||
|
||||
Flask will automatically detect the factory (``create_app`` or ``make_app``)
|
||||
in ``myapp``. You can also pass arguments to the factory like this::
|
||||
|
||||
$ flask --app hello run
|
||||
|
||||
Flask will automatically detect the factory if it is named
|
||||
``create_app`` or ``make_app`` in ``hello``. You can also pass arguments
|
||||
to the factory like this:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ flask --app 'hello:create_app(local_auth=True)' run
|
||||
|
||||
Then the ``create_app`` factory in ``hello`` is called with the keyword
|
||||
argument ``local_auth=True``. See :doc:`/cli` for more detail.
|
||||
export FLASK_APP="myapp:create_app('dev')"
|
||||
flask run
|
||||
|
||||
Then the ``create_app`` factory in ``myapp`` is called with the string
|
||||
``'dev'`` as the argument. See :doc:`/cli` for more detail.
|
||||
|
||||
Factory Improvements
|
||||
--------------------
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _caching-pattern:
|
||||
|
||||
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
|
||||
time.
|
||||
|
||||
Flask itself does not provide caching for you, but `Flask-Caching`_, an
|
||||
extension for Flask does. Flask-Caching supports various backends, and it is
|
||||
even possible to develop your own caching backend.
|
||||
Flask itself does not provide caching for you, but Werkzeug, one of the
|
||||
libraries it is based on, has some very basic cache support. It supports
|
||||
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.org/>`_) 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,101 @@
|
|||
Background Tasks with Celery
|
||||
============================
|
||||
Celery Background Tasks
|
||||
=======================
|
||||
|
||||
If your application has a long running task, such as processing some uploaded data or
|
||||
sending email, you don't want to wait for it to finish during a request. Instead, use a
|
||||
task queue to send the necessary data to another process that will run the task in the
|
||||
background while the request returns immediately.
|
||||
|
||||
`Celery`_ is a powerful task queue that can be used for simple background tasks as well
|
||||
as complex multi-stage programs and schedules. This guide will show you how to configure
|
||||
Celery using Flask. Read Celery's `First Steps with Celery`_ guide to learn how to use
|
||||
Celery itself.
|
||||
|
||||
.. _Celery: https://celery.readthedocs.io
|
||||
.. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html
|
||||
|
||||
The Flask repository contains `an example <https://github.com/pallets/flask/tree/main/examples/celery>`_
|
||||
based on the information on this page, which also shows how to use JavaScript to submit
|
||||
tasks and poll for progress and results.
|
||||
If your application has a long running task, such as processing some uploaded
|
||||
data or sending email, you don't want to wait for it to finish during a
|
||||
request. Instead, use a task queue to send the necessary data to another
|
||||
process that will run the task in the background while the request returns
|
||||
immediately.
|
||||
|
||||
Celery is a powerful task queue that can be used for simple background tasks
|
||||
as well as complex multi-stage programs and schedules. This guide will show you
|
||||
how to configure Celery using Flask, but assumes you've already read the
|
||||
`First Steps with Celery <http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
|
||||
guide in the Celery documentation.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
Install Celery from PyPI, for example using pip:
|
||||
|
||||
.. code-block:: text
|
||||
Celery is a separate Python package. Install it from PyPI using pip::
|
||||
|
||||
$ pip install celery
|
||||
|
||||
Configure
|
||||
---------
|
||||
|
||||
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
|
||||
it through Flask's config, and to let tasks access the Flask application.
|
||||
For instance you can place this in a ``tasks`` module. While you can use
|
||||
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
|
||||
and registers tasks. While creating a Flask app, use the following code to create and
|
||||
configure a Celery app as well.
|
||||
This is all that is necessary to properly integrate Celery with Flask::
|
||||
|
||||
.. 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'],
|
||||
broker=app.config['CELERY_BROKER_URL']
|
||||
)
|
||||
celery.conf.update(app.config)
|
||||
|
||||
def celery_init_app(app: Flask) -> Celery:
|
||||
class FlaskTask(Task):
|
||||
def __call__(self, *args: object, **kwargs: object) -> object:
|
||||
class ContextTask(celery.Task):
|
||||
def __call__(self, *args, **kwargs):
|
||||
with app.app_context():
|
||||
return self.run(*args, **kwargs)
|
||||
|
||||
celery_app = Celery(app.name, task_cls=FlaskTask)
|
||||
celery_app.config_from_object(app.config["CELERY"])
|
||||
celery_app.set_default()
|
||||
app.extensions["celery"] = celery_app
|
||||
return celery_app
|
||||
celery.Task = ContextTask
|
||||
return celery
|
||||
|
||||
This creates and returns a ``Celery`` app object. Celery `configuration`_ is taken from
|
||||
the ``CELERY`` key in the Flask configuration. The Celery app is set as the default, so
|
||||
that it is seen during each request. The ``Task`` subclass automatically runs task
|
||||
functions with a Flask app context active, so that services like your database
|
||||
connections are available.
|
||||
The function creates a new Celery object, configures it with the broker
|
||||
from the application config, updates the rest of the Celery config from
|
||||
the Flask config and then creates a subclass of the task that wraps the
|
||||
task execution in an application context.
|
||||
|
||||
.. _configuration: https://celery.readthedocs.io/en/stable/userguide/configuration.html
|
||||
An example task
|
||||
---------------
|
||||
|
||||
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
|
||||
Let's write a task that adds two numbers together and returns the result. We
|
||||
configure Celery's broker and backend to use Redis, create a ``celery``
|
||||
application using the factor from above, and then use it to define the task. ::
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_mapping(
|
||||
CELERY=dict(
|
||||
broker_url="redis://localhost",
|
||||
result_backend="redis://localhost",
|
||||
task_ignore_result=True,
|
||||
),
|
||||
flask_app = Flask(__name__)
|
||||
flask_app.config.update(
|
||||
CELERY_BROKER_URL='redis://localhost:6379',
|
||||
CELERY_RESULT_BACKEND='redis://localhost:6379'
|
||||
)
|
||||
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
|
||||
-------------------
|
||||
|
||||
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:
|
||||
@celery.task()
|
||||
def add_together(a, b):
|
||||
return a + b
|
||||
|
||||
Earlier, we configured Celery to ignore task results by default. Since we want to know
|
||||
the return value of this task, we set ``ignore_result=False``. On the other hand, a task
|
||||
that didn't need a result, such as sending an email, wouldn't set this.
|
||||
This task can now be called in the background::
|
||||
|
||||
result = add_together.delay(23, 42)
|
||||
result.wait() # 65
|
||||
|
||||
Calling Tasks
|
||||
-------------
|
||||
Run a worker
|
||||
------------
|
||||
|
||||
The decorated function becomes a task object with methods to call it in the background.
|
||||
The simplest way is to use the ``delay(*args, **kwargs)`` method. See Celery's docs for
|
||||
more methods.
|
||||
If you jumped in and already executed the above code you will be
|
||||
disappointed to learn that ``.wait()`` will never actually return.
|
||||
That's because you also need to run a Celery worker to receive and execute the
|
||||
task. ::
|
||||
|
||||
A Celery worker must be running to run the task. Starting a worker is shown in the
|
||||
previous sections.
|
||||
$ celery -A your_application.celery worker
|
||||
|
||||
.. 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)
|
||||
Now that the worker is running, ``wait`` will return the result once the task
|
||||
is finished.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _deferred-callbacks:
|
||||
|
||||
Deferred Request Callbacks
|
||||
==========================
|
||||
|
||||
|
|
@ -14,7 +16,7 @@ response object.
|
|||
|
||||
One way is to avoid the situation. Very often that is possible. For instance
|
||||
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
||||
callback instead. However, sometimes moving code there makes it
|
||||
callback instead. However, sometimes moving code there makes it more
|
||||
more complicated or awkward to reason about.
|
||||
|
||||
As an alternative, you can use :func:`~flask.after_this_request` to register
|
||||
|
|
@ -39,6 +41,5 @@ user in a cookie in a :meth:`~flask.Flask.before_request` callback::
|
|||
@after_this_request
|
||||
def remember_language(response):
|
||||
response.set_cookie('user_lang', language)
|
||||
return response
|
||||
|
||||
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 -Db ''
|
||||
|
||||
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://pypi.org/project/setuptools/
|
||||
99
docs/patterns/errorpages.rst
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
.. _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 that returns a response when a type of error is
|
||||
raised, similar to how a view is a function that returns a response when a
|
||||
request URL is matched. It is passed the instance of the error being handled,
|
||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
|
||||
handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
||||
addition to explicit 500 errors.
|
||||
|
||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||
decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
|
||||
can be registered for a status code, like 404, or for an exception class.
|
||||
|
||||
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.
|
||||
|
||||
A handler for "500 Internal Server Error" will not be used when running in
|
||||
debug mode. Instead, the interactive debugger will be shown.
|
||||
|
||||
Here is an example implementation for a "404 Page Not Found" exception::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# note that we set the 404 status explicitly
|
||||
return render_template('404.html'), 404
|
||||
|
||||
When using the :ref:`application factory pattern <app-factories>`::
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(404, page_not_found)
|
||||
return app
|
||||
|
||||
An example template might be this:
|
||||
|
||||
.. 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
|
||||
however your application is the root you can simply route a redirect::
|
||||
|
||||
app.add_url_rule(
|
||||
"/favicon.ico",
|
||||
endpoint="favicon",
|
||||
redirect_to=url_for("static", filename="favicon.ico"),
|
||||
)
|
||||
app.add_url_rule('/favicon.ico',
|
||||
redirect_to=url_for('static', filename='favicon.ico'))
|
||||
|
||||
If you want to save the extra redirect request you can also write a view
|
||||
using :func:`~flask.send_from_directory`::
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _uploading-files:
|
||||
|
||||
Uploading Files
|
||||
===============
|
||||
|
||||
|
|
@ -23,7 +25,7 @@ bootstrapping code for our application::
|
|||
from werkzeug.utils import secure_filename
|
||||
|
||||
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.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
|
||||
sending out the data to the client. That way you can make sure that users
|
||||
are not able to upload HTML files that would cause XSS problems (see
|
||||
:ref:`security-xss`). Also make sure to disallow ``.php`` files if the server
|
||||
:ref:`xss`). Also make sure to disallow ``.php`` files if the server
|
||||
executes them, but who has PHP installed on their server, right? :)
|
||||
|
||||
Next the functions that check if an extension is valid and that uploads
|
||||
|
|
@ -55,15 +57,16 @@ the file and redirects the user to the URL for the uploaded file::
|
|||
flash('No file part')
|
||||
return redirect(request.url)
|
||||
file = request.files['file']
|
||||
# If the user does not select a file, the browser submits an
|
||||
# empty file without a filename.
|
||||
# if user does not select file, browser also
|
||||
# submit an empty part without filename
|
||||
if file.filename == '':
|
||||
flash('No selected file')
|
||||
return redirect(request.url)
|
||||
if file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||
return redirect(url_for('download_file', name=filename))
|
||||
return redirect(url_for('uploaded_file',
|
||||
filename=filename))
|
||||
return '''
|
||||
<!doctype html>
|
||||
<title>Upload new File</title>
|
||||
|
|
@ -101,28 +104,31 @@ before storing it directly on the filesystem.
|
|||
>>> secure_filename('../../../../home/username/.bashrc')
|
||||
'home_username_.bashrc'
|
||||
|
||||
We want to be able to serve the uploaded files so they can be downloaded
|
||||
by users. We'll define a ``download_file`` view to serve files in the
|
||||
upload folder by name. ``url_for("download_file", name=name)`` generates
|
||||
download URLs.
|
||||
|
||||
.. code-block:: python
|
||||
Now one last thing is missing: the serving of the uploaded files. In the
|
||||
:func:`upload_file()` we redirect the user to
|
||||
``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``.
|
||||
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::
|
||||
|
||||
from flask import send_from_directory
|
||||
|
||||
@app.route('/uploads/<name>')
|
||||
def download_file(name):
|
||||
return send_from_directory(app.config["UPLOAD_FOLDER"], name)
|
||||
@app.route('/uploads/<filename>')
|
||||
def uploaded_file(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
||||
filename)
|
||||
|
||||
If you're using middleware or the HTTP server to serve files, you can
|
||||
register the ``download_file`` endpoint as ``build_only`` so ``url_for``
|
||||
will work without a view function.
|
||||
Alternatively you can register `uploaded_file` as `build_only` rule and
|
||||
use the :class:`~werkzeug.wsgi.SharedDataMiddleware`. This also works with
|
||||
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(
|
||||
"/uploads/<name>", endpoint="download_file", build_only=True
|
||||
)
|
||||
If you now run the application everything should work as expected.
|
||||
|
||||
|
||||
Improving Uploads
|
||||
|
|
@ -131,17 +137,17 @@ Improving Uploads
|
|||
.. versionadded:: 0.6
|
||||
|
||||
So how exactly does Flask handle uploads? Well it will store them in the
|
||||
webserver's memory if the files are reasonably small, otherwise in a
|
||||
webserver's memory if the files are reasonable small otherwise in a
|
||||
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
||||
do you specify the maximum file size after which an upload is aborted? By
|
||||
default Flask will happily accept file uploads with an unlimited amount of
|
||||
default Flask will happily accept file uploads to an unlimited amount of
|
||||
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
||||
config key::
|
||||
|
||||
from flask import Flask, Request
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000
|
||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
||||
|
||||
The code above will limit the maximum allowed payload to 16 megabytes.
|
||||
If a larger file is transmitted, Flask will raise a
|
||||
|
|
@ -163,9 +169,10 @@ Upload Progress Bars
|
|||
|
||||
A while ago many developers had the idea to read the incoming file in
|
||||
small chunks and store the upload progress in the database to be able to
|
||||
poll the progress with JavaScript from the client. The client asks the
|
||||
server every 5 seconds how much it has transmitted, but this is
|
||||
something it should already know.
|
||||
poll the progress with JavaScript from the client. Long story short: the
|
||||
client asks the server every 5 seconds how much it has transmitted
|
||||
already. Do you realize the irony? The client is asking for something it
|
||||
should already know.
|
||||
|
||||
An Easier Solution
|
||||
------------------
|
||||
|
|
@ -175,8 +182,9 @@ are JavaScript libraries like jQuery_ that have form plugins to ease the
|
|||
construction of progress bar.
|
||||
|
||||
Because the common pattern for file uploads exists almost unchanged in all
|
||||
applications dealing with uploads, there are also some Flask extensions that
|
||||
implement a full fledged upload mechanism that allows controlling which
|
||||
file extensions are allowed to be uploaded.
|
||||
applications dealing with uploads, there is also a Flask extension called
|
||||
`Flask-Uploads`_ that implements a full fledged upload mechanism with white and
|
||||
blacklisting of extensions and more.
|
||||
|
||||
.. _jQuery: https://jquery.com/
|
||||
.. _Flask-Uploads: https://pythonhosted.org/Flask-Uploads/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _message-flashing-pattern:
|
||||
|
||||
Message Flashing
|
||||
================
|
||||
|
||||
|
|
@ -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 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
|
||||
:func:`~flask.get_flashed_messages` function to also return the
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
.. _patterns:
|
||||
|
||||
Patterns for Flask
|
||||
==================
|
||||
|
||||
Certain features and interactions are common enough that you will find
|
||||
them in most web applications. For example, many applications use a
|
||||
relational database and user authentication. They will open a database
|
||||
connection at the beginning of the request and get the information for
|
||||
the logged in user. At the end of the request, the database connection
|
||||
is closed.
|
||||
Certain things are common enough that the chances are high you will find
|
||||
them in most web applications. For example quite a lot of applications
|
||||
are using relational databases and user authentication. In that case,
|
||||
chances are they will open a database connection at the beginning of the
|
||||
request and get the information of the currently logged in user. At the
|
||||
end of the request, the database connection is closed again.
|
||||
|
||||
These types of patterns may be a bit outside the scope of Flask itself,
|
||||
but Flask makes it easy to implement them. Some common patterns are
|
||||
collected in the following pages.
|
||||
There are more user contributed snippets and patterns in the `Flask
|
||||
Snippet Archives <http://flask.pocoo.org/snippets/>`_.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
@ -18,7 +19,10 @@ collected in the following pages.
|
|||
packages
|
||||
appfactories
|
||||
appdispatch
|
||||
apierrors
|
||||
urlprocessors
|
||||
distribute
|
||||
fabric
|
||||
sqlite3
|
||||
sqlalchemy
|
||||
fileuploads
|
||||
|
|
@ -27,9 +31,10 @@ collected in the following pages.
|
|||
wtforms
|
||||
templateinheritance
|
||||
flashing
|
||||
javascript
|
||||
jquery
|
||||
errorpages
|
||||
lazyloading
|
||||
mongoengine
|
||||
mongokit
|
||||
favicon
|
||||
streaming
|
||||
deferredcallbacks
|
||||
|
|
@ -37,4 +42,3 @@ collected in the following pages.
|
|||
requestchecksum
|
||||
celery
|
||||
subclassing
|
||||
singlepageapplications
|
||||
|
|
|
|||