Merge branch 'master' into json-object-hook
This commit is contained in:
commit
ea2e9609bc
42 changed files with 1100 additions and 663 deletions
35
.github/ISSUE_TEMPLATE.rst
vendored
35
.github/ISSUE_TEMPLATE.rst
vendored
|
|
@ -1,2 +1,33 @@
|
||||||
The issue tracker is a tool to address bugs.
|
**This issue tracker is a tool to address bugs in Flask itself.
|
||||||
Please use the #pocoo IRC channel on freenode or Stack Overflow for questions.
|
Please use the #pocoo IRC channel on freenode or Stack Overflow for general
|
||||||
|
questions about using Jinja or issues not related to Jinja.**
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
|
||||||
16
.github/PULL_REQUEST_TEMPLATE.rst
vendored
Normal file
16
.github/PULL_REQUEST_TEMPLATE.rst
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.
|
||||||
|
-->
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "docs/_themes"]
|
|
||||||
path = docs/_themes
|
|
||||||
url = https://github.com/mitsuhiko/flask-sphinx-themes.git
|
|
||||||
42
.travis.yml
42
.travis.yml
|
|
@ -4,49 +4,25 @@ language: python
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: 3.4
|
- python: 3.4
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: 3.3
|
- python: 3.3
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: 2.6
|
- python: 2.6
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: pypy
|
- python: pypy
|
||||||
env: TOXENV=py-release,codecov
|
env: TOXENV=py,codecov
|
||||||
- python: nightly
|
- python: nightly
|
||||||
env: TOXENV=py-release
|
env: TOXENV=py
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: TOXENV=docs-html
|
env: TOXENV=docs-html
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: TOXENV=py-release-simplejson,codecov
|
env: TOXENV=py-simplejson,codecov
|
||||||
- python: 2.7
|
|
||||||
env: TOXENV=py-release-simplejson,codecov
|
|
||||||
- python: pypy
|
|
||||||
env: TOXENV=py-release-simplejson,codecov
|
|
||||||
- python: 3.6
|
|
||||||
env: TOXENV=py-devel,codecov
|
|
||||||
- python: 3.3
|
|
||||||
env: TOXENV=py-devel,codecov
|
|
||||||
- python: 2.7
|
|
||||||
env: TOXENV=py-devel,codecov
|
|
||||||
- python: 2.6
|
|
||||||
env: TOXENV=py-devel,codecov
|
|
||||||
- python: pypy
|
|
||||||
env: TOXENV=py-devel,codecov
|
|
||||||
- python: 3.6
|
|
||||||
env: TOXENV=py-lowest,codecov
|
|
||||||
- python: 3.3
|
|
||||||
env: TOXENV=py-lowest,codecov
|
|
||||||
- python: 2.7
|
|
||||||
env: TOXENV=py-lowest,codecov
|
|
||||||
- python: 2.6
|
|
||||||
env: TOXENV=py-lowest,codecov
|
|
||||||
- python: pypy
|
|
||||||
env: TOXENV=py-lowest,codecov
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
|
|
|
||||||
44
CHANGES
44
CHANGES
|
|
@ -8,16 +8,19 @@ Version 0.13
|
||||||
|
|
||||||
Major release, unreleased
|
Major release, unreleased
|
||||||
|
|
||||||
- Make `app.run()` into a noop if a Flask application is run from the
|
- Minimum Werkzeug version bumped to 0.9, but please use the latest version.
|
||||||
development server on the command line. This avoids some behavior that
|
- Minimum Click version bumped to 4, but please use the latest version.
|
||||||
|
- Make ``app.run()`` into a noop if a Flask application is run from the
|
||||||
|
development server on the command line. This avoids some behavior that
|
||||||
was confusing to debug for newcomers.
|
was confusing to debug for newcomers.
|
||||||
- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify()
|
- Change default configuration ``JSONIFY_PRETTYPRINT_REGULAR=False``.
|
||||||
method returns compressed response by default, and pretty response in
|
``jsonify()`` method returns compressed response by default, and pretty
|
||||||
debug mode.
|
response in debug mode. (`#2193`_)
|
||||||
- Change Flask.__init__ to accept two new keyword arguments, ``host_matching``
|
- Change ``Flask.__init__`` to accept two new keyword arguments,
|
||||||
and ``static_host``. This enables ``host_matching`` to be set properly by the
|
``host_matching`` and ``static_host``. This enables ``host_matching`` to be
|
||||||
time the constructor adds the static route, and enables the static route to
|
set properly by the time the constructor adds the static route, and enables
|
||||||
be properly associated with the required host. (``#1559``)
|
the static route to be properly associated with the required host.
|
||||||
|
(``#1559``)
|
||||||
- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_)
|
- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_)
|
||||||
- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``.
|
- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``.
|
||||||
(`#2017`_)
|
(`#2017`_)
|
||||||
|
|
@ -46,18 +49,41 @@ Major release, unreleased
|
||||||
work with the ``flask`` command. If they take a single parameter or a
|
work with the ``flask`` command. If they take a single parameter or a
|
||||||
parameter named ``script_info``, the ``ScriptInfo`` object will be passed.
|
parameter named ``script_info``, the ``ScriptInfo`` object will be passed.
|
||||||
(`#2319`_)
|
(`#2319`_)
|
||||||
|
- FLASK_APP=myproject.app:create_app('dev') support.
|
||||||
|
- ``FLASK_APP`` can be set to an app factory, with arguments if needed, for
|
||||||
|
example ``FLASK_APP=myproject.app:create_app('dev')``. (`#2326`_)
|
||||||
|
- ``View.provide_automatic_options = True`` is set on the view function from
|
||||||
|
``View.as_view``, to be detected in ``app.add_url_rule``. (`#2316`_)
|
||||||
|
- Error handling will try handlers registered for ``blueprint, code``,
|
||||||
|
``app, code``, ``blueprint, exception``, ``app, exception``. (`#2314`_)
|
||||||
|
- ``Cookie`` is added to the response's ``Vary`` header if the session is
|
||||||
|
accessed at all during the request (and it wasn't deleted). (`#2288`_)
|
||||||
|
- ``app.test_request_context()`` take ``subdomain`` and ``url_scheme``
|
||||||
|
parameters for use when building base URL. (`#1621`_)
|
||||||
|
- Set ``APPLICATION_ROOT = '/'`` by default. This was already the implicit
|
||||||
|
default when it was set to ``None``.
|
||||||
|
- ``TRAP_BAD_REQUEST_ERRORS`` is enabled by default in debug mode.
|
||||||
|
``BadRequestKeyError`` has a message with the bad key in debug mode instead
|
||||||
|
of the generic bad request message. (`#2348`_)
|
||||||
|
|
||||||
.. _#1489: https://github.com/pallets/flask/pull/1489
|
.. _#1489: https://github.com/pallets/flask/pull/1489
|
||||||
|
.. _#1621: https://github.com/pallets/flask/pull/1621
|
||||||
.. _#1898: https://github.com/pallets/flask/pull/1898
|
.. _#1898: https://github.com/pallets/flask/pull/1898
|
||||||
.. _#1936: https://github.com/pallets/flask/pull/1936
|
.. _#1936: https://github.com/pallets/flask/pull/1936
|
||||||
.. _#2017: https://github.com/pallets/flask/pull/2017
|
.. _#2017: https://github.com/pallets/flask/pull/2017
|
||||||
|
.. _#2193: https://github.com/pallets/flask/pull/2193
|
||||||
.. _#2223: https://github.com/pallets/flask/pull/2223
|
.. _#2223: https://github.com/pallets/flask/pull/2223
|
||||||
.. _#2254: https://github.com/pallets/flask/pull/2254
|
.. _#2254: https://github.com/pallets/flask/pull/2254
|
||||||
.. _#2256: https://github.com/pallets/flask/pull/2256
|
.. _#2256: https://github.com/pallets/flask/pull/2256
|
||||||
.. _#2259: https://github.com/pallets/flask/pull/2259
|
.. _#2259: https://github.com/pallets/flask/pull/2259
|
||||||
.. _#2282: https://github.com/pallets/flask/pull/2282
|
.. _#2282: https://github.com/pallets/flask/pull/2282
|
||||||
|
.. _#2288: https://github.com/pallets/flask/pull/2288
|
||||||
.. _#2297: https://github.com/pallets/flask/pull/2297
|
.. _#2297: https://github.com/pallets/flask/pull/2297
|
||||||
|
.. _#2314: https://github.com/pallets/flask/pull/2314
|
||||||
|
.. _#2316: https://github.com/pallets/flask/pull/2316
|
||||||
.. _#2319: https://github.com/pallets/flask/pull/2319
|
.. _#2319: https://github.com/pallets/flask/pull/2319
|
||||||
|
.. _#2326: https://github.com/pallets/flask/pull/2326
|
||||||
|
.. _#2348: https://github.com/pallets/flask/pull/2348
|
||||||
|
|
||||||
Version 0.12.2
|
Version 0.12.2
|
||||||
--------------
|
--------------
|
||||||
|
|
|
||||||
185
CONTRIBUTING.rst
185
CONTRIBUTING.rst
|
|
@ -1,46 +1,75 @@
|
||||||
==========================
|
|
||||||
How to contribute to Flask
|
How to contribute to Flask
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Thanks for considering contributing to Flask.
|
Thank you for considering contributing to Flask!
|
||||||
|
|
||||||
Support questions
|
Support questions
|
||||||
=================
|
-----------------
|
||||||
|
|
||||||
Please, don't use the issue tracker for this. Check whether the ``#pocoo`` IRC
|
Please, don't use the issue tracker for this. Use one of the following
|
||||||
channel on Freenode can help with your issue. If your problem is not strictly
|
resources for questions about your own code:
|
||||||
Werkzeug or Flask specific, ``#python`` is generally more active.
|
|
||||||
`Stack Overflow <https://stackoverflow.com/>`_ is also worth considering.
|
* 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
|
Reporting issues
|
||||||
================
|
----------------
|
||||||
|
|
||||||
- Under which versions of Python does this happen? This is even more important
|
- Describe what you expected to happen.
|
||||||
if your issue is encoding related.
|
- 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.
|
||||||
|
|
||||||
- Under which versions of Werkzeug does this happen? Check if this issue is
|
.. _minimal, complete, and verifiable example: https://stackoverflow.com/help/mcve
|
||||||
fixed in the repository.
|
|
||||||
|
|
||||||
Submitting patches
|
Submitting patches
|
||||||
==================
|
------------------
|
||||||
|
|
||||||
- Include tests if your patch is supposed to solve a bug, and explain
|
- 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
|
clearly under which circumstances the bug happens. Make sure the test fails
|
||||||
without your patch.
|
without your patch.
|
||||||
|
- Try to follow `PEP8`_, but you may ignore the line length limit if following
|
||||||
- Try to follow `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_, but you
|
it would make the code uglier.
|
||||||
may ignore the line-length-limit if following it would make the code uglier.
|
|
||||||
|
|
||||||
First time setup
|
First time setup
|
||||||
----------------
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- Download and install the `latest version of git`_.
|
- Download and install the `latest version of git`_.
|
||||||
- Configure git with your `username`_ and `email`_.
|
- 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`_.
|
- Make sure you have a `GitHub account`_.
|
||||||
- Fork Flask to your GitHub account by clicking the `Fork`_ button.
|
- Fork Flask to your GitHub account by clicking the `Fork`_ button.
|
||||||
- `Clone`_ your GitHub fork locally.
|
- `Clone`_ your GitHub fork locally::
|
||||||
- Add the main repository as a remote to update later.
|
|
||||||
``git remote add pallets https://github.com/pallets/flask``
|
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
|
.. _GitHub account: https://github.com/join
|
||||||
.. _latest version of git: https://git-scm.com/downloads
|
.. _latest version of git: https://git-scm.com/downloads
|
||||||
|
|
@ -50,7 +79,7 @@ First time setup
|
||||||
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
|
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
|
||||||
|
|
||||||
Start coding
|
Start coding
|
||||||
------------
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
- Create a branch to identify the issue you would like to work on (e.g.
|
- Create a branch to identify the issue you would like to work on (e.g.
|
||||||
``2287-dry-test-suite``)
|
``2287-dry-test-suite``)
|
||||||
|
|
@ -68,98 +97,70 @@ Start coding
|
||||||
|
|
||||||
.. _contributing-testsuite:
|
.. _contributing-testsuite:
|
||||||
|
|
||||||
Running the testsuite
|
Running the tests
|
||||||
---------------------
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
You probably want to set up a `virtualenv
|
Run the basic test suite with::
|
||||||
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
|
||||||
|
|
||||||
The minimal requirement for running the testsuite is ``pytest``. You can
|
pytest
|
||||||
install it with::
|
|
||||||
|
|
||||||
pip install 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.
|
||||||
|
|
||||||
Clone this repository::
|
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.6, 2.7, 3.3,
|
||||||
|
3.4, 3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run::
|
||||||
|
|
||||||
git clone https://github.com/pallets/flask.git
|
tox
|
||||||
|
|
||||||
Install Flask as an editable package using the current source::
|
|
||||||
|
|
||||||
cd flask
|
|
||||||
pip install --editable .
|
|
||||||
|
|
||||||
Running the testsuite
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
The minimal requirement for running the testsuite is ``pytest``. You can
|
|
||||||
install it with::
|
|
||||||
|
|
||||||
pip install pytest
|
|
||||||
|
|
||||||
Then you can run the testsuite with::
|
|
||||||
|
|
||||||
pytest tests/
|
|
||||||
|
|
||||||
**Shortcut**: ``make test`` will ensure ``pytest`` is installed, and run it.
|
|
||||||
|
|
||||||
With only pytest installed, a large part of the testsuite will get skipped
|
|
||||||
though. Whether this is relevant depends on which part of Flask you're working
|
|
||||||
on. Travis is set up to run the full testsuite when you submit your pull
|
|
||||||
request anyways.
|
|
||||||
|
|
||||||
If you really want to test everything, you will have to install ``tox`` instead
|
|
||||||
of ``pytest``. You can install it with::
|
|
||||||
|
|
||||||
pip install tox
|
|
||||||
|
|
||||||
The ``tox`` command will then run all tests against multiple combinations
|
|
||||||
Python versions and dependency versions.
|
|
||||||
|
|
||||||
**Shortcut**: ``make tox-test`` will ensure ``tox`` is installed, and run it.
|
|
||||||
|
|
||||||
Running test coverage
|
Running test coverage
|
||||||
---------------------
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
Generating a report of lines that do not have unit test coverage can indicate where
|
|
||||||
to start contributing. ``pytest`` integrates with ``coverage.py``, using the ``pytest-cov``
|
|
||||||
plugin. This assumes you have already run the testsuite (see previous section)::
|
|
||||||
|
|
||||||
pip install pytest-cov
|
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::
|
||||||
|
|
||||||
After this has been installed, you can output a report to the command line using this command::
|
coverage run -m pytest
|
||||||
|
coverage report
|
||||||
|
coverage html
|
||||||
|
# then open htmlcov/index.html
|
||||||
|
|
||||||
pytest --cov=flask tests/
|
Read more about `coverage <https://coverage.readthedocs.io>`_.
|
||||||
|
|
||||||
Generate a HTML report can be done using this command::
|
Running the full test suite with ``tox`` will combine the coverage reports
|
||||||
|
from all runs.
|
||||||
|
|
||||||
pytest --cov-report html --cov=flask tests/
|
``make`` targets
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io
|
Flask provides a ``Makefile`` with various shortcuts. They will ensure that
|
||||||
|
all dependencies are installed.
|
||||||
|
|
||||||
**Shortcut**: ``make cov`` will ensure ``pytest-cov`` is installed, run it, display the results, *and* save the HTML report.
|
- ``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
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
Caution
|
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
|
||||||
pushing
|
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
|
||||||
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.
|
turn off repository checks in the admin panel.
|
||||||
|
|
||||||
|
These files can also cause issues while cloning. If you have ::
|
||||||
cloning
|
|
||||||
-------
|
|
||||||
The zero-padded file modes files above can cause issues while cloning, too. If you have
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[fetch]
|
[fetch]
|
||||||
fsckobjects = true
|
fsckobjects = true
|
||||||
|
|
||||||
or
|
or ::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
[receive]
|
[receive]
|
||||||
fsckObjects = true
|
fsckObjects = true
|
||||||
|
|
||||||
|
set in your git configuration file, cloning this repository will fail. The only
|
||||||
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.
|
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.
|
||||||
|
|
|
||||||
36
Makefile
36
Makefile
|
|
@ -1,14 +1,28 @@
|
||||||
.PHONY: clean-pyc ext-test test tox-test test-with-mem upload-docs docs audit
|
.PHONY: all install-dev test coverage cov test-all tox docs audit release clean-pyc upload-docs ebook
|
||||||
|
|
||||||
all: clean-pyc test
|
all: test
|
||||||
|
|
||||||
test:
|
install-dev:
|
||||||
pip install -r test-requirements.txt
|
pip install -q -e .[dev]
|
||||||
tox -e py-release
|
|
||||||
|
|
||||||
cov:
|
test: clean-pyc install-dev
|
||||||
pip install -r test-requirements.txt -q
|
pytest
|
||||||
FLASK_DEBUG= py.test --cov-report term --cov-report html --cov=flask --cov=examples tests examples
|
|
||||||
|
coverage: clean-pyc install-dev
|
||||||
|
pip install -q -e .[test]
|
||||||
|
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:
|
audit:
|
||||||
python setup.py audit
|
python setup.py audit
|
||||||
|
|
@ -16,9 +30,6 @@ audit:
|
||||||
release:
|
release:
|
||||||
python scripts/make-release.py
|
python scripts/make-release.py
|
||||||
|
|
||||||
ext-test:
|
|
||||||
python tests/flaskext_test.py --browse
|
|
||||||
|
|
||||||
clean-pyc:
|
clean-pyc:
|
||||||
find . -name '*.pyc' -exec rm -f {} +
|
find . -name '*.pyc' -exec rm -f {} +
|
||||||
find . -name '*.pyo' -exec rm -f {} +
|
find . -name '*.pyo' -exec rm -f {} +
|
||||||
|
|
@ -40,6 +51,3 @@ ebook:
|
||||||
@echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).'
|
@echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).'
|
||||||
@echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.'
|
@echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.'
|
||||||
ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher'
|
ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher'
|
||||||
|
|
||||||
docs:
|
|
||||||
$(MAKE) -C docs html
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9
|
|
||||||
|
|
@ -5,31 +5,37 @@ The Application Context
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
.. versionadded:: 0.9
|
||||||
|
|
||||||
One of the design ideas behind Flask is that there are two different
|
One of the design ideas behind Flask is that there are at least two
|
||||||
“states” in which code is executed. The application setup state in which
|
different “states” in which code is executed:
|
||||||
the application implicitly is on the module level. It starts when the
|
|
||||||
:class:`Flask` object is instantiated, and it implicitly ends when the
|
|
||||||
first request comes in. While the application is in this state a few
|
|
||||||
assumptions are true:
|
|
||||||
|
|
||||||
- the programmer can modify the application object safely.
|
1. The application setup state, in which the application implicitly is
|
||||||
- no request handling happened so far
|
on the module level.
|
||||||
- you have to have a reference to the application object in order to
|
|
||||||
modify it, there is no magic proxy that can give you a reference to
|
|
||||||
the application object you're currently creating or modifying.
|
|
||||||
|
|
||||||
In contrast, during request handling, a couple of other rules exist:
|
This state starts when the :class:`Flask` object is instantiated, and
|
||||||
|
it implicitly ends when the first request comes in. While the
|
||||||
|
application is in this state, a few assumptions are true:
|
||||||
|
|
||||||
- while a request is active, the context local objects
|
- the programmer can modify the application object safely.
|
||||||
(:data:`flask.request` and others) point to the current request.
|
- no request handling happened so far
|
||||||
- any code can get hold of these objects at any time.
|
- you have to have a reference to the application object in order to
|
||||||
|
modify it, there is no magic proxy that can give you a reference to
|
||||||
|
the application object you're currently creating or modifying.
|
||||||
|
|
||||||
There is a third state which is sitting in between a little bit.
|
2. In contrast, in the request handling state, a couple of other rules
|
||||||
Sometimes you are dealing with an application in a way that is similar to
|
exist:
|
||||||
how you interact with applications during request handling; just that there
|
|
||||||
is no request active. Consider, for instance, that you're sitting in an
|
- while a request is active, the context local objects
|
||||||
interactive Python shell and interacting with the application, or a
|
(:data:`flask.request` and others) point to the current request.
|
||||||
command line application.
|
- any code can get hold of these objects at any time.
|
||||||
|
|
||||||
|
3. There is also a third state somewhere in between 'module-level' and
|
||||||
|
'request-handling':
|
||||||
|
|
||||||
|
Sometimes you are dealing with an application in a way that is similar to
|
||||||
|
how you interact with applications during request handling, but without
|
||||||
|
there being an active request. Consider, for instance, that you're
|
||||||
|
sitting in an interactive Python shell and interacting with the
|
||||||
|
application, or a command line application.
|
||||||
|
|
||||||
The application context is what powers the :data:`~flask.current_app`
|
The application context is what powers the :data:`~flask.current_app`
|
||||||
context local.
|
context local.
|
||||||
|
|
|
||||||
21
docs/conf.py
21
docs/conf.py
|
|
@ -22,7 +22,6 @@ BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), '_themes'))
|
|
||||||
sys.path.append(os.path.dirname(__file__))
|
sys.path.append(os.path.dirname(__file__))
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
@ -121,7 +120,7 @@ exclude_patterns = ['_build']
|
||||||
# html_theme_options = {}
|
# html_theme_options = {}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
html_theme_path = ['_themes']
|
# html_theme_path = ['_themes']
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
|
|
@ -273,21 +272,9 @@ intersphinx_mapping = {
|
||||||
'blinker': ('https://pythonhosted.org/blinker/', None)
|
'blinker': ('https://pythonhosted.org/blinker/', None)
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
html_theme_options = {
|
||||||
__import__('flask_theme_support')
|
'touch_icon': 'touch-icon.png'
|
||||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
}
|
||||||
html_theme = 'flask'
|
|
||||||
html_theme_options = {
|
|
||||||
'touch_icon': 'touch-icon.png'
|
|
||||||
}
|
|
||||||
except ImportError:
|
|
||||||
print('-' * 74)
|
|
||||||
print('Warning: Flask themes unavailable. Building with default theme')
|
|
||||||
print('If you want the Flask themes, run this command and build again:')
|
|
||||||
print()
|
|
||||||
print(' git submodule update --init')
|
|
||||||
print('-' * 74)
|
|
||||||
|
|
||||||
|
|
||||||
# unwrap decorators
|
# unwrap decorators
|
||||||
def unwrap_decorators():
|
def unwrap_decorators():
|
||||||
|
|
|
||||||
396
docs/config.rst
396
docs/config.rst
|
|
@ -3,8 +3,6 @@
|
||||||
Configuration Handling
|
Configuration Handling
|
||||||
======================
|
======================
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
|
||||||
|
|
||||||
Applications need some kind of configuration. There are different settings
|
Applications need some kind of configuration. There are different settings
|
||||||
you might want to change depending on the application environment like
|
you might want to change depending on the application environment like
|
||||||
toggling the debug mode, setting the secret key, and other such
|
toggling the debug mode, setting the secret key, and other such
|
||||||
|
|
@ -64,172 +62,243 @@ Builtin Configuration Values
|
||||||
|
|
||||||
The following configuration values are used internally by Flask:
|
The following configuration values are used internally by Flask:
|
||||||
|
|
||||||
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
|
.. py:data:: DEBUG
|
||||||
|
|
||||||
================================= =========================================
|
Enable debug mode. When using the development server with ``flask run`` or
|
||||||
``DEBUG`` enable/disable debug mode when using
|
``app.run``, an interactive debugger will be shown for unhanlded
|
||||||
``Flask.run()`` method to start server
|
exceptions, and the server will be reloaded when code changes.
|
||||||
``TESTING`` enable/disable testing mode
|
|
||||||
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
|
|
||||||
propagation of exceptions. If not set or
|
|
||||||
explicitly set to ``None`` this is
|
|
||||||
implicitly true if either ``TESTING`` or
|
|
||||||
``DEBUG`` is true.
|
|
||||||
``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in
|
|
||||||
debug mode the request context is not
|
|
||||||
popped on exceptions to enable debuggers
|
|
||||||
to introspect the data. This can be
|
|
||||||
disabled by this key. You can also use
|
|
||||||
this setting to force-enable it for non
|
|
||||||
debug execution which might be useful to
|
|
||||||
debug production applications (but also
|
|
||||||
very risky).
|
|
||||||
``SECRET_KEY`` the secret key
|
|
||||||
``SESSION_COOKIE_NAME`` the name of the session cookie
|
|
||||||
``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If
|
|
||||||
this is not set, the cookie will be
|
|
||||||
valid for all subdomains of
|
|
||||||
``SERVER_NAME``.
|
|
||||||
``SESSION_COOKIE_PATH`` the path for the session cookie. If
|
|
||||||
this is not set the cookie will be valid
|
|
||||||
for all of ``APPLICATION_ROOT`` or if
|
|
||||||
that is not set for ``'/'``.
|
|
||||||
``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set
|
|
||||||
with the httponly flag. Defaults to
|
|
||||||
``True``.
|
|
||||||
``SESSION_COOKIE_SECURE`` controls if the cookie should be set
|
|
||||||
with the secure flag. Defaults to
|
|
||||||
``False``.
|
|
||||||
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
|
||||||
:class:`datetime.timedelta` object.
|
|
||||||
Starting with Flask 0.8 this can also be
|
|
||||||
an integer representing seconds.
|
|
||||||
``SESSION_REFRESH_EACH_REQUEST`` this flag controls how permanent
|
|
||||||
sessions are refreshed. If set to ``True``
|
|
||||||
(which is the default) then the cookie
|
|
||||||
is refreshed each request which
|
|
||||||
automatically bumps the lifetime. If
|
|
||||||
set to ``False`` a `set-cookie` header is
|
|
||||||
only sent if the session is modified.
|
|
||||||
Non permanent sessions are not affected
|
|
||||||
by this.
|
|
||||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
|
||||||
``LOGGER_NAME`` the name of the logger
|
|
||||||
``LOGGER_HANDLER_POLICY`` the policy of the default logging
|
|
||||||
handler. The default is ``'always'``
|
|
||||||
which means that the default logging
|
|
||||||
handler is always active. ``'debug'``
|
|
||||||
will only activate logging in debug
|
|
||||||
mode, ``'production'`` will only log in
|
|
||||||
production and ``'never'`` disables it
|
|
||||||
entirely.
|
|
||||||
``SERVER_NAME`` the name and port number of the server.
|
|
||||||
Required for subdomain support (e.g.:
|
|
||||||
``'myapp.dev:5000'``) Note that
|
|
||||||
localhost does not support subdomains so
|
|
||||||
setting this to “localhost” does not
|
|
||||||
help. Setting a ``SERVER_NAME`` also
|
|
||||||
by default enables URL generation
|
|
||||||
without a request context but with an
|
|
||||||
application context.
|
|
||||||
``APPLICATION_ROOT`` The path value used for the session
|
|
||||||
cookie if ``SESSION_COOKIE_PATH`` isn't
|
|
||||||
set. If it's also ``None`` ``'/'`` is used.
|
|
||||||
Note that to actually serve your Flask
|
|
||||||
app under a subpath you need to tell
|
|
||||||
your WSGI container the ``SCRIPT_NAME``
|
|
||||||
WSGI environment variable.
|
|
||||||
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
|
||||||
reject incoming requests with a
|
|
||||||
content length greater than this by
|
|
||||||
returning a 413 status code.
|
|
||||||
``SEND_FILE_MAX_AGE_DEFAULT`` Default cache control max age to use with
|
|
||||||
:meth:`~flask.Flask.send_static_file` (the
|
|
||||||
default static file handler) and
|
|
||||||
:func:`~flask.send_file`, as
|
|
||||||
:class:`datetime.timedelta` or as seconds.
|
|
||||||
Override this value on a per-file
|
|
||||||
basis using the
|
|
||||||
:meth:`~flask.Flask.get_send_file_max_age`
|
|
||||||
hook on :class:`~flask.Flask` or
|
|
||||||
:class:`~flask.Blueprint`,
|
|
||||||
respectively. Defaults to 43200 (12 hours).
|
|
||||||
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
|
|
||||||
not execute the error handlers of HTTP
|
|
||||||
exceptions but instead treat the
|
|
||||||
exception like any other and bubble it
|
|
||||||
through the exception stack. This is
|
|
||||||
helpful for hairy debugging situations
|
|
||||||
where you have to find out where an HTTP
|
|
||||||
exception is coming from.
|
|
||||||
``TRAP_BAD_REQUEST_ERRORS`` Werkzeug's internal data structures that
|
|
||||||
deal with request specific data will
|
|
||||||
raise special key errors that are also
|
|
||||||
bad request exceptions. Likewise many
|
|
||||||
operations can implicitly fail with a
|
|
||||||
BadRequest exception for consistency.
|
|
||||||
Since it's nice for debugging to know
|
|
||||||
why exactly it failed this flag can be
|
|
||||||
used to debug those situations. If this
|
|
||||||
config is set to ``True`` you will get
|
|
||||||
a regular traceback instead.
|
|
||||||
``PREFERRED_URL_SCHEME`` The URL scheme that should be used for
|
|
||||||
URL generation if no URL scheme is
|
|
||||||
available. This defaults to ``http``.
|
|
||||||
``JSON_AS_ASCII`` By default Flask serialize object to
|
|
||||||
ascii-encoded JSON. If this is set to
|
|
||||||
``False`` Flask will not encode to ASCII
|
|
||||||
and output strings as-is and return
|
|
||||||
unicode strings. ``jsonify`` will
|
|
||||||
automatically encode it in ``utf-8``
|
|
||||||
then for transport for instance.
|
|
||||||
``JSON_SORT_KEYS`` By default Flask will serialize JSON
|
|
||||||
objects in a way that the keys are
|
|
||||||
ordered. This is done in order to
|
|
||||||
ensure that independent of the hash seed
|
|
||||||
of the dictionary the return value will
|
|
||||||
be consistent to not trash external HTTP
|
|
||||||
caches. You can override the default
|
|
||||||
behavior by changing this variable.
|
|
||||||
This is not recommended but might give
|
|
||||||
you a performance improvement on the
|
|
||||||
cost of cacheability.
|
|
||||||
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` or the Flask app
|
|
||||||
is running in debug mode, jsonify responses
|
|
||||||
will be pretty printed.
|
|
||||||
``JSONIFY_MIMETYPE`` MIME type used for jsonify responses.
|
|
||||||
``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of
|
|
||||||
the template source and reload it
|
|
||||||
automatically. By default the value is
|
|
||||||
``None`` which means that Flask checks
|
|
||||||
original file only in debug mode.
|
|
||||||
``EXPLAIN_TEMPLATE_LOADING`` If this is enabled then every attempt to
|
|
||||||
load a template will write an info
|
|
||||||
message to the logger explaining the
|
|
||||||
attempts to locate the template. This
|
|
||||||
can be useful to figure out why
|
|
||||||
templates cannot be found or wrong
|
|
||||||
templates appear to be loaded.
|
|
||||||
================================= =========================================
|
|
||||||
|
|
||||||
.. admonition:: More on ``SERVER_NAME``
|
**Do not enable debug mode in production.**
|
||||||
|
|
||||||
The ``SERVER_NAME`` key is used for the subdomain support. Because
|
Default: ``False``
|
||||||
Flask cannot guess the subdomain part without the knowledge of the
|
|
||||||
actual server name, this is required if you want to work with
|
|
||||||
subdomains. This is also used for the session cookie.
|
|
||||||
|
|
||||||
Please keep in mind that not only Flask has the problem of not knowing
|
.. py:data:: TESTING
|
||||||
what subdomains are, your web browser does as well. Most modern web
|
|
||||||
browsers will not allow cross-subdomain cookies to be set on a
|
|
||||||
server name without dots in it. So if your server name is
|
|
||||||
``'localhost'`` you will not be able to set a cookie for
|
|
||||||
``'localhost'`` and every subdomain of it. Please choose a different
|
|
||||||
server name in that case, like ``'myapplication.local'`` and add
|
|
||||||
this name + the subdomains you want to use into your host config
|
|
||||||
or setup a local `bind`_.
|
|
||||||
|
|
||||||
.. _bind: https://www.isc.org/downloads/bind/
|
Enable testing mode. Exceptions are propagated rather than handled by the
|
||||||
|
the app's error handlers. Extensions may also change their behavior to
|
||||||
|
facilitate easier testing. You should enable this in your own tests.
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
.. py:data:: PROPAGATE_EXCEPTIONS
|
||||||
|
|
||||||
|
Exceptions are re-raised rather than being handled by the app's error
|
||||||
|
handlers. If not set, this is implicitly true if ``TESTING`` or ``DEBUG``
|
||||||
|
is enabled.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: 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
|
||||||
|
to be handled by the interactive debugger instead of returning it as a
|
||||||
|
simple error response.
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
.. py:data:: TRAP_BAD_REQUEST_ERRORS``
|
||||||
|
|
||||||
|
Trying to access a key that doesn't exist from request dicts like ``args``
|
||||||
|
and ``form`` will return a 400 Bad Request error page. Enable this to treat
|
||||||
|
the error as an unhandled exception instead so that you get the interactive
|
||||||
|
debugger. This is a more specific version of ``TRAP_HTTP_EXCEPTIONS``. If
|
||||||
|
unset, it is enabled in debug mode.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: SECRET_KEY
|
||||||
|
|
||||||
|
A secret key that will be used for securely signing the session cookie
|
||||||
|
and can be used for any other security related needs by extensions or your
|
||||||
|
application. It should be a long random string of bytes, although unicode
|
||||||
|
is accepted too. For example, copy the output of this to your config::
|
||||||
|
|
||||||
|
python -c 'import os; print(os.urandom(32))'
|
||||||
|
|
||||||
|
**Do not reveal the secret key when posting questions or committing code.**
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: SESSION_COOKIE_NAME
|
||||||
|
|
||||||
|
The name of the session cookie. Can be changed in case you already have a
|
||||||
|
cookie with the same name.
|
||||||
|
|
||||||
|
Default: ``'session'``
|
||||||
|
|
||||||
|
.. py:data:: SESSION_COOKIE_DOMAIN
|
||||||
|
|
||||||
|
The domain match rule that the session cookie will be valid for. If not
|
||||||
|
set, the cookie will be valid for all subdomains of ``SERVER_NAME``. If
|
||||||
|
``False``, the cookie's domain will not be set.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: SESSION_COOKIE_PATH
|
||||||
|
|
||||||
|
The path that the session cookie will be valid for. If not set, the cookie
|
||||||
|
will be valid underneath ``APPLICATION_ROOT`` or ``/`` if that is not set.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: SESSION_COOKIE_HTTPONLY
|
||||||
|
|
||||||
|
Browsers will not allow JavaScript access to cookies marked as "HTTP only"
|
||||||
|
for security.
|
||||||
|
|
||||||
|
Default: ``True``
|
||||||
|
|
||||||
|
.. py:data:: SESSION_COOKIE_SECURE
|
||||||
|
|
||||||
|
Browsers will only send cookies with requests over HTTPS if the cookie is
|
||||||
|
marked "secure". The application must be served over HTTPS for this to make
|
||||||
|
sense.
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
.. py:data:: PERMANENT_SESSION_LIFETIME
|
||||||
|
|
||||||
|
If ``session.permanent`` is true, the cookie's max age will be set to this
|
||||||
|
number of seconds. Can either be a :class:`datetime.timedelta` or an
|
||||||
|
``int``.
|
||||||
|
|
||||||
|
Default: ``timedelta(days=31)`` (``2678400`` seconds)
|
||||||
|
|
||||||
|
.. py:data:: SESSION_REFRESH_EACH_REQUEST
|
||||||
|
|
||||||
|
Control whether the cookie is sent with every response when
|
||||||
|
``session.permanent`` is true. Sending the cookie every time (the default)
|
||||||
|
can more reliably keep the session from expiring, but uses more bandwidth.
|
||||||
|
Non-permanent sessions are not affected.
|
||||||
|
|
||||||
|
Default: ``True``
|
||||||
|
|
||||||
|
.. py:data:: USE_X_SENDFILE
|
||||||
|
|
||||||
|
When serving files, set the ``X-Sendfile`` header instead of serving the
|
||||||
|
data with Flask. Some web servers, such as Apache, recognize this and serve
|
||||||
|
the data more efficiently. This only makes sense when using such a server.
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
.. py:data:: SEND_FILE_MAX_AGE_DEFAULT
|
||||||
|
|
||||||
|
When serving files, set the cache control max age to this number of
|
||||||
|
seconds. Can 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.
|
||||||
|
|
||||||
|
Default: ``timedelta(hours=12)`` (``43200`` seconds)
|
||||||
|
|
||||||
|
.. py:data:: LOGGER_NAME
|
||||||
|
|
||||||
|
The name of the logger that the Flask application sets up. If not set,
|
||||||
|
it will take the import name passed to ``Flask.__init__``.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: LOGGER_HANDLER_POLICY
|
||||||
|
|
||||||
|
When to activate the application's logger handler. ``'always'`` always
|
||||||
|
enables it, ``'debug'`` only activates it in debug mode, ``'production'``
|
||||||
|
only activates it when not in debug mode, and ``'never'`` never enables it.
|
||||||
|
|
||||||
|
Default: ``'always'``
|
||||||
|
|
||||||
|
.. py:data:: SERVER_NAME
|
||||||
|
|
||||||
|
Inform the application what host and port it is bound to. Required for
|
||||||
|
subdomain route matching support.
|
||||||
|
|
||||||
|
If set, will be used for the session cookie domain if
|
||||||
|
``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. ::
|
||||||
|
|
||||||
|
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``
|
||||||
|
|
||||||
|
.. py:data:: APPLICATION_ROOT
|
||||||
|
|
||||||
|
Inform the application what path it is mounted under by the application /
|
||||||
|
web server.
|
||||||
|
|
||||||
|
Will be used for the session cookie path if ``SESSION_COOKIE_PATH`` is not
|
||||||
|
set.
|
||||||
|
|
||||||
|
Default: ``'/'``
|
||||||
|
|
||||||
|
.. py:data:: PREFERRED_URL_SCHEME
|
||||||
|
|
||||||
|
Use this scheme for generating external URLs when not in a request context.
|
||||||
|
|
||||||
|
Default: ``'http'``
|
||||||
|
|
||||||
|
.. py:data:: MAX_CONTENT_LENGTH
|
||||||
|
|
||||||
|
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``
|
||||||
|
|
||||||
|
.. py:data:: JSON_AS_ASCII
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Default: ``True``
|
||||||
|
|
||||||
|
.. py:data:: JSON_SORT_KEYS
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Default: ``True``
|
||||||
|
|
||||||
|
.. py:data:: JSONIFY_PRETTYPRINT_REGULAR
|
||||||
|
|
||||||
|
``jsonify`` responses will be output with newlines, spaces, and indentation
|
||||||
|
for easier reading by humans. Always enabled in debug mode.
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
.. py:data:: JSONIFY_MIMETYPE
|
||||||
|
|
||||||
|
The mimetype of ``jsonify`` responses.
|
||||||
|
|
||||||
|
Default: ``'application/json'``
|
||||||
|
|
||||||
|
.. py:data:: TEMPLATES_AUTO_RELOAD
|
||||||
|
|
||||||
|
Reload templates when they are changed. If not set, it will be enabled in
|
||||||
|
debug mode.
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
.. py:data:: EXPLAIN_TEMPLATE_LOADING
|
||||||
|
|
||||||
|
Log debugging information tracing how a template file was loaded. This can
|
||||||
|
be useful to figure out why a template was not loaded or the wrong file
|
||||||
|
appears to be loaded.
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
.. versionadded:: 0.4
|
.. versionadded:: 0.4
|
||||||
``LOGGER_NAME``
|
``LOGGER_NAME``
|
||||||
|
|
@ -477,3 +546,4 @@ Example usage for both::
|
||||||
# or via open_instance_resource:
|
# or via open_instance_resource:
|
||||||
with app.open_instance_resource('application.cfg') as f:
|
with app.open_instance_resource('application.cfg') as f:
|
||||||
config = f.read()
|
config = f.read()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,4 @@ Design notes, legal information and changelog are here for the interested.
|
||||||
upgrading
|
upgrading
|
||||||
changelog
|
changelog
|
||||||
license
|
license
|
||||||
|
contributing
|
||||||
|
|
|
||||||
1
docs/contributing.rst
Normal file
1
docs/contributing.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.. include:: ../CONTRIBUTING.rst
|
||||||
|
|
@ -76,49 +76,72 @@ Error handlers
|
||||||
You might want to show custom error pages to the user when an error occurs.
|
You might want to show custom error pages to the user when an error occurs.
|
||||||
This can be done by registering error handlers.
|
This can be done by registering error handlers.
|
||||||
|
|
||||||
Error handlers are normal :ref:`views` but instead of being registered for
|
An error handler is a normal view function that return a response, but instead
|
||||||
routes, they are registered for exceptions that are raised while trying to
|
of being registered for a route, it is registered for an exception or HTTP
|
||||||
do something else.
|
status code that would is raised while trying to handle a request.
|
||||||
|
|
||||||
Registering
|
Registering
|
||||||
```````````
|
```````````
|
||||||
|
|
||||||
Register error handlers using :meth:`~flask.Flask.errorhandler` or
|
Register handlers by decorating a function with
|
||||||
:meth:`~flask.Flask.register_error_handler`::
|
: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. ::
|
||||||
|
|
||||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||||
def handle_bad_request(e):
|
def handle_bad_request(e):
|
||||||
return 'bad request!'
|
return 'bad request!', 400
|
||||||
|
|
||||||
app.register_error_handler(400, lambda e: 'bad request!')
|
# or, without the decorator
|
||||||
|
app.register_error_handler(400, handle_bad_request)
|
||||||
|
|
||||||
Those two ways are equivalent, but the first one is more clear and leaves
|
|
||||||
you with a function to call on your whim (and in tests). Note that
|
|
||||||
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
:exc:`werkzeug.exceptions.HTTPException` subclasses like
|
||||||
:exc:`~werkzeug.exceptions.BadRequest` from the example and their HTTP codes
|
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
|
||||||
are interchangeable when handed to the registration methods or decorator
|
when registering handlers. (``BadRequest.code == 400``)
|
||||||
(``BadRequest.code == 400``).
|
|
||||||
|
|
||||||
You are however not limited to :exc:`~werkzeug.exceptions.HTTPException`
|
Non-standard HTTP codes cannot be registered by code because they are not known
|
||||||
or HTTP status codes but can register a handler for every exception class you
|
by Werkzeug. Instead, define a subclass of
|
||||||
like.
|
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
||||||
|
register and raise that exception class. ::
|
||||||
|
|
||||||
.. versionchanged:: 0.11
|
class InsufficientStorage(werkzeug.exceptions.HTTPException):
|
||||||
|
code = 507
|
||||||
|
description = 'Not enough storage space.'
|
||||||
|
|
||||||
Errorhandlers are now prioritized by specificity of the exception classes
|
app.register_error_handler(InsuffcientStorage, handle_507)
|
||||||
they are registered for instead of the order they are registered in.
|
|
||||||
|
raise InsufficientStorage()
|
||||||
|
|
||||||
|
Handlers can be registered for any exception class, not just
|
||||||
|
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
|
||||||
|
codes. Handlers can be registered for a specific class, or for all subclasses
|
||||||
|
of a parent class.
|
||||||
|
|
||||||
Handling
|
Handling
|
||||||
````````
|
````````
|
||||||
|
|
||||||
Once an exception instance is raised, its class hierarchy is traversed,
|
When an exception is caught by Flask while handling a request, it is first
|
||||||
and searched for in the exception classes for which handlers are registered.
|
looked up by code. If no handler is registered for the code, it is looked up
|
||||||
The most specific handler is selected.
|
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.
|
||||||
|
|
||||||
E.g. if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
|
For example, if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
|
||||||
is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`,
|
is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`,
|
||||||
the more specific :exc:`ConnectionRefusedError` handler is called on the
|
the more specific :exc:`ConnectionRefusedError` handler is called with the
|
||||||
exception instance, and its response is shown to the user.
|
exception instance to generate the response.
|
||||||
|
|
||||||
|
Handlers registered on the blueprint take precedence over those registered
|
||||||
|
globally on the application, assuming a blueprint is handling the request that
|
||||||
|
raises the exception. However, the blueprint cannot handle 404 routing errors
|
||||||
|
because the 404 occurs at the routing level before the blueprint can be
|
||||||
|
determined.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.11
|
||||||
|
|
||||||
|
Handlers are prioritized by specificity of the exception classes they are
|
||||||
|
registered for instead of the order they are registered in.
|
||||||
|
|
||||||
Error Mails
|
Error Mails
|
||||||
-----------
|
-----------
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
# flasky extensions. flasky pygments style based on tango style
|
|
||||||
from pygments.style import Style
|
|
||||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
|
||||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskyStyle(Style):
|
|
||||||
background_color = "#f8f8f8"
|
|
||||||
default_style = ""
|
|
||||||
|
|
||||||
styles = {
|
|
||||||
# No corresponding class for the following:
|
|
||||||
#Text: "", # class: ''
|
|
||||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
|
||||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
|
||||||
Other: "#000000", # class 'x'
|
|
||||||
|
|
||||||
Comment: "italic #8f5902", # class: 'c'
|
|
||||||
Comment.Preproc: "noitalic", # class: 'cp'
|
|
||||||
|
|
||||||
Keyword: "bold #004461", # class: 'k'
|
|
||||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
|
||||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
|
||||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
|
||||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
|
||||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
|
||||||
Keyword.Type: "bold #004461", # class: 'kt'
|
|
||||||
|
|
||||||
Operator: "#582800", # class: 'o'
|
|
||||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
|
||||||
|
|
||||||
Punctuation: "bold #000000", # class: 'p'
|
|
||||||
|
|
||||||
# because special names such as Name.Class, Name.Function, etc.
|
|
||||||
# are not recognized as such later in the parsing, we choose them
|
|
||||||
# to look the same as ordinary variables.
|
|
||||||
Name: "#000000", # class: 'n'
|
|
||||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
|
||||||
Name.Builtin: "#004461", # class: 'nb'
|
|
||||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
|
||||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
|
||||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
|
||||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
|
||||||
Name.Entity: "#ce5c00", # class: 'ni'
|
|
||||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
|
||||||
Name.Function: "#000000", # class: 'nf'
|
|
||||||
Name.Property: "#000000", # class: 'py'
|
|
||||||
Name.Label: "#f57900", # class: 'nl'
|
|
||||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
|
||||||
Name.Other: "#000000", # class: 'nx'
|
|
||||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
|
||||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
|
||||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
|
||||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
|
||||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
|
||||||
|
|
||||||
Number: "#990000", # class: 'm'
|
|
||||||
|
|
||||||
Literal: "#000000", # class: 'l'
|
|
||||||
Literal.Date: "#000000", # class: 'ld'
|
|
||||||
|
|
||||||
String: "#4e9a06", # class: 's'
|
|
||||||
String.Backtick: "#4e9a06", # class: 'sb'
|
|
||||||
String.Char: "#4e9a06", # class: 'sc'
|
|
||||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
|
||||||
String.Double: "#4e9a06", # class: 's2'
|
|
||||||
String.Escape: "#4e9a06", # class: 'se'
|
|
||||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
|
||||||
String.Interpol: "#4e9a06", # class: 'si'
|
|
||||||
String.Other: "#4e9a06", # class: 'sx'
|
|
||||||
String.Regex: "#4e9a06", # class: 'sr'
|
|
||||||
String.Single: "#4e9a06", # class: 's1'
|
|
||||||
String.Symbol: "#4e9a06", # class: 'ss'
|
|
||||||
|
|
||||||
Generic: "#000000", # class: 'g'
|
|
||||||
Generic.Deleted: "#a40000", # class: 'gd'
|
|
||||||
Generic.Emph: "italic #000000", # class: 'ge'
|
|
||||||
Generic.Error: "#ef2929", # class: 'gr'
|
|
||||||
Generic.Heading: "bold #000080", # class: 'gh'
|
|
||||||
Generic.Inserted: "#00A000", # class: 'gi'
|
|
||||||
Generic.Output: "#888", # class: 'go'
|
|
||||||
Generic.Prompt: "#745334", # class: 'gp'
|
|
||||||
Generic.Strong: "bold #000000", # class: 'gs'
|
|
||||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
|
||||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
|
||||||
}
|
|
||||||
|
|
@ -104,3 +104,105 @@ vulnerabilities
|
||||||
<https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so
|
<https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so
|
||||||
this behavior was changed and :func:`~flask.jsonify` now supports serializing
|
this behavior was changed and :func:`~flask.jsonify` now supports serializing
|
||||||
arrays.
|
arrays.
|
||||||
|
|
||||||
|
Security Headers
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Browsers recognize various response headers in order to control security. We
|
||||||
|
recommend reviewing each of the headers below for use in your application.
|
||||||
|
The `Flask-Talisman`_ extension can be used to manage HTTPS and the security
|
||||||
|
headers for you.
|
||||||
|
|
||||||
|
.. _Flask-Talisman: https://github.com/GoogleCloudPlatform/flask-talisman
|
||||||
|
|
||||||
|
HTTP Strict Transport Security (HSTS)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tells the browser to convert all HTTP requests to HTTPS, preventing
|
||||||
|
man-in-the-middle (MITM) attacks. ::
|
||||||
|
|
||||||
|
response.haders['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
||||||
|
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
||||||
|
|
||||||
|
Content Security Policy (CSP)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tell the browser where it can load various types of resource from. This header
|
||||||
|
should be used whenever possible, but requires some work to define the correct
|
||||||
|
policy for your site. A very strict policy would be::
|
||||||
|
|
||||||
|
response.headers['Content-Security-Policy'] = "default-src: 'self'"
|
||||||
|
|
||||||
|
- https://csp.withgoogle.com/docs/index.html
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||||
|
|
||||||
|
X-Content-Type-Options
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Forces the browser to honor the response content type instead of trying to
|
||||||
|
detect it, which can be abused to generate a cross-site scripting (XSS)
|
||||||
|
attack. ::
|
||||||
|
|
||||||
|
response.headers['X-Content-Type-Options'] = 'nosniff'
|
||||||
|
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||||
|
|
||||||
|
X-Frame-Options
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Prevents external sites from embedding your site in an ``iframe``. This
|
||||||
|
prevents a class of attacks where clicks in the outer frame can be translated
|
||||||
|
invisibly to clicks on your page's elements. This is also known as
|
||||||
|
"clickjacking". ::
|
||||||
|
|
||||||
|
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||||
|
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||||
|
|
||||||
|
X-XSS-Protection
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The browser will try to prevent reflected XSS attacks by not loading the page
|
||||||
|
if the request contains something that looks like JavaScript and the response
|
||||||
|
contains the same data. ::
|
||||||
|
|
||||||
|
response.headers['X-XSS-Protection'] = '1; mode=block'
|
||||||
|
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
|
||||||
|
|
||||||
|
Set-Cookie options
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
These options can be added to a ``Set-Cookie`` header to improve their
|
||||||
|
security. Flask has configuration options to set these on the session cookie.
|
||||||
|
They can be set on other cookies too.
|
||||||
|
|
||||||
|
- ``Secure`` limits cookies to HTTPS traffic only.
|
||||||
|
- ``HttpOnly`` protects the contents of cookies from being read with
|
||||||
|
JavaScript.
|
||||||
|
- ``SameSite`` ensures that cookies can only be requested from the same
|
||||||
|
domain that created them. It is not supported by Flask yet.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
app.config.update(
|
||||||
|
SESSION_COOKIE_SECURE=True,
|
||||||
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
response.set_cookie('username', 'flask', secure=True, httponly=True)
|
||||||
|
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies
|
||||||
|
|
||||||
|
HTTP Public Key Pinning (HPKP)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This tells the browser to authenticate with the server using only the specific
|
||||||
|
certificate key to prevent MITM attacks.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Be careful when enabling this, as it is very difficult to undo if you set up
|
||||||
|
or upgrade your key incorrectly.
|
||||||
|
|
||||||
|
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@
|
||||||
|
|
||||||
~ How do I use it?
|
~ How do I use it?
|
||||||
|
|
||||||
1. edit the configuration in the flaskr.py file or
|
1. edit the configuration in the factory.py file or
|
||||||
export an FLASKR_SETTINGS environment variable
|
export an FLASKR_SETTINGS environment variable
|
||||||
pointing to a configuration file.
|
pointing to a configuration file or pass in a
|
||||||
|
dictionary with config values using the create_app
|
||||||
|
function.
|
||||||
|
|
||||||
2. install the app from the root of the project directory
|
2. install the app from the root of the project directory
|
||||||
|
|
||||||
|
|
@ -19,7 +21,7 @@
|
||||||
|
|
||||||
3. Instruct flask to use the right application
|
3. Instruct flask to use the right application
|
||||||
|
|
||||||
export FLASK_APP=flaskr
|
export FLASK_APP=flaskr.factory:create_app()
|
||||||
|
|
||||||
4. initialize the database with this command:
|
4. initialize the database with this command:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from .flaskr import app
|
|
||||||
0
examples/flaskr/flaskr/blueprints/__init__.py
Normal file
0
examples/flaskr/flaskr/blueprints/__init__.py
Normal file
|
|
@ -10,29 +10,18 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
from sqlite3 import dbapi2 as sqlite3
|
from sqlite3 import dbapi2 as sqlite3
|
||||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
from flask import Blueprint, request, session, g, redirect, url_for, abort, \
|
||||||
render_template, flash
|
render_template, flash, current_app
|
||||||
|
|
||||||
|
|
||||||
# create our little application :)
|
# create our blueprint :)
|
||||||
app = Flask(__name__)
|
bp = Blueprint('flaskr', __name__)
|
||||||
|
|
||||||
# Load default config and override config from an environment variable
|
|
||||||
app.config.update(dict(
|
|
||||||
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
|
|
||||||
DEBUG=True,
|
|
||||||
SECRET_KEY='development key',
|
|
||||||
USERNAME='admin',
|
|
||||||
PASSWORD='default'
|
|
||||||
))
|
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
|
||||||
|
|
||||||
|
|
||||||
def connect_db():
|
def connect_db():
|
||||||
"""Connects to the specific database."""
|
"""Connects to the specific database."""
|
||||||
rv = sqlite3.connect(app.config['DATABASE'])
|
rv = sqlite3.connect(current_app.config['DATABASE'])
|
||||||
rv.row_factory = sqlite3.Row
|
rv.row_factory = sqlite3.Row
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
@ -40,18 +29,11 @@ def connect_db():
|
||||||
def init_db():
|
def init_db():
|
||||||
"""Initializes the database."""
|
"""Initializes the database."""
|
||||||
db = get_db()
|
db = get_db()
|
||||||
with app.open_resource('schema.sql', mode='r') as f:
|
with current_app.open_resource('schema.sql', mode='r') as f:
|
||||||
db.cursor().executescript(f.read())
|
db.cursor().executescript(f.read())
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
@app.cli.command('initdb')
|
|
||||||
def initdb_command():
|
|
||||||
"""Creates the database tables."""
|
|
||||||
init_db()
|
|
||||||
print('Initialized the database.')
|
|
||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
"""Opens a new database connection if there is none yet for the
|
"""Opens a new database connection if there is none yet for the
|
||||||
current application context.
|
current application context.
|
||||||
|
|
@ -61,14 +43,7 @@ def get_db():
|
||||||
return g.sqlite_db
|
return g.sqlite_db
|
||||||
|
|
||||||
|
|
||||||
@app.teardown_appcontext
|
@bp.route('/')
|
||||||
def close_db(error):
|
|
||||||
"""Closes the database again at the end of the request."""
|
|
||||||
if hasattr(g, 'sqlite_db'):
|
|
||||||
g.sqlite_db.close()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def show_entries():
|
def show_entries():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cur = db.execute('select title, text from entries order by id desc')
|
cur = db.execute('select title, text from entries order by id desc')
|
||||||
|
|
@ -76,7 +51,7 @@ def show_entries():
|
||||||
return render_template('show_entries.html', entries=entries)
|
return render_template('show_entries.html', entries=entries)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/add', methods=['POST'])
|
@bp.route('/add', methods=['POST'])
|
||||||
def add_entry():
|
def add_entry():
|
||||||
if not session.get('logged_in'):
|
if not session.get('logged_in'):
|
||||||
abort(401)
|
abort(401)
|
||||||
|
|
@ -85,26 +60,26 @@ def add_entry():
|
||||||
[request.form['title'], request.form['text']])
|
[request.form['title'], request.form['text']])
|
||||||
db.commit()
|
db.commit()
|
||||||
flash('New entry was successfully posted')
|
flash('New entry was successfully posted')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('flaskr.show_entries'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@bp.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
error = None
|
error = None
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if request.form['username'] != app.config['USERNAME']:
|
if request.form['username'] != current_app.config['USERNAME']:
|
||||||
error = 'Invalid username'
|
error = 'Invalid username'
|
||||||
elif request.form['password'] != app.config['PASSWORD']:
|
elif request.form['password'] != current_app.config['PASSWORD']:
|
||||||
error = 'Invalid password'
|
error = 'Invalid password'
|
||||||
else:
|
else:
|
||||||
session['logged_in'] = True
|
session['logged_in'] = True
|
||||||
flash('You were logged in')
|
flash('You were logged in')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('flaskr.show_entries'))
|
||||||
return render_template('login.html', error=error)
|
return render_template('login.html', error=error)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/logout')
|
@bp.route('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
session.pop('logged_in', None)
|
session.pop('logged_in', None)
|
||||||
flash('You were logged out')
|
flash('You were logged out')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('flaskr.show_entries'))
|
||||||
64
examples/flaskr/flaskr/factory.py
Normal file
64
examples/flaskr/flaskr/factory.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Flaskr
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
A microblog example application written as Flask tutorial with
|
||||||
|
Flask and sqlite3.
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from flask import Flask, g
|
||||||
|
from werkzeug.utils import find_modules, import_string
|
||||||
|
from flaskr.blueprints.flaskr import init_db
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(config=None):
|
||||||
|
app = Flask('flaskr')
|
||||||
|
|
||||||
|
app.config.update(dict(
|
||||||
|
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
|
||||||
|
DEBUG=True,
|
||||||
|
SECRET_KEY='development key',
|
||||||
|
USERNAME='admin',
|
||||||
|
PASSWORD='default'
|
||||||
|
))
|
||||||
|
app.config.update(config or {})
|
||||||
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
|
register_blueprints(app)
|
||||||
|
register_cli(app)
|
||||||
|
register_teardowns(app)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def register_blueprints(app):
|
||||||
|
"""Register all blueprint modules
|
||||||
|
|
||||||
|
Reference: Armin Ronacher, "Flask for Fun and for Profit" PyBay 2016.
|
||||||
|
"""
|
||||||
|
for name in find_modules('flaskr.blueprints'):
|
||||||
|
mod = import_string(name)
|
||||||
|
if hasattr(mod, 'bp'):
|
||||||
|
app.register_blueprint(mod.bp)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def register_cli(app):
|
||||||
|
@app.cli.command('initdb')
|
||||||
|
def initdb_command():
|
||||||
|
"""Creates the database tables."""
|
||||||
|
init_db()
|
||||||
|
print('Initialized the database.')
|
||||||
|
|
||||||
|
|
||||||
|
def register_teardowns(app):
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def close_db(error):
|
||||||
|
"""Closes the database again at the end of the request."""
|
||||||
|
if hasattr(g, 'sqlite_db'):
|
||||||
|
g.sqlite_db.close()
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
<h1>Flaskr</h1>
|
<h1>Flaskr</h1>
|
||||||
<div class="metanav">
|
<div class="metanav">
|
||||||
{% if not session.logged_in %}
|
{% if not session.logged_in %}
|
||||||
<a href="{{ url_for('login') }}">log in</a>
|
<a href="{{ url_for('flaskr.login') }}">log in</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ url_for('logout') }}">log out</a>
|
<a href="{{ url_for('flaskr.logout') }}">log out</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% for message in get_flashed_messages() %}
|
{% for message in get_flashed_messages() %}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
{% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %}
|
{% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %}
|
||||||
<form action="{{ url_for('login') }}" method="post">
|
<form action="{{ url_for('flaskr.login') }}" method="post">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Username:
|
<dt>Username:
|
||||||
<dd><input type="text" name="username">
|
<dd><input type="text" name="username">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% if session.logged_in %}
|
{% if session.logged_in %}
|
||||||
<form action="{{ url_for('add_entry') }}" method="post" class="add-entry">
|
<form action="{{ url_for('flaskr.add_entry') }}" method="post" class="add-entry">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Title:
|
<dt>Title:
|
||||||
<dd><input type="text" size="30" name="title">
|
<dd><input type="text" size="30" name="title">
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
[tool:pytest]
|
[aliases]
|
||||||
test=pytest
|
test=pytest
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
from setuptools import setup
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Flaskr Tests
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the Flaskr application.
|
||||||
|
|
||||||
|
:copyright: (c) 2015 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='flaskr',
|
name='flaskr',
|
||||||
packages=['flaskr'],
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'flask',
|
'flask',
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,38 @@
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import pytest
|
import pytest
|
||||||
from flaskr import flaskr
|
from flaskr.factory import create_app
|
||||||
|
from flaskr.blueprints.flaskr import init_db
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client():
|
def app(request):
|
||||||
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
|
||||||
flaskr.app.config['TESTING'] = True
|
db_fd, temp_db_location = tempfile.mkstemp()
|
||||||
client = flaskr.app.test_client()
|
config = {
|
||||||
with flaskr.app.app_context():
|
'DATABASE': temp_db_location,
|
||||||
flaskr.init_db()
|
'TESTING': True,
|
||||||
yield client
|
'DB_FD': db_fd
|
||||||
os.close(db_fd)
|
}
|
||||||
os.unlink(flaskr.app.config['DATABASE'])
|
|
||||||
|
app = create_app(config=config)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
init_db()
|
||||||
|
yield app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(request, app):
|
||||||
|
|
||||||
|
client = app.test_client()
|
||||||
|
|
||||||
|
def teardown():
|
||||||
|
os.close(app.config['DB_FD'])
|
||||||
|
os.unlink(app.config['DATABASE'])
|
||||||
|
request.addfinalizer(teardown)
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
def login(client, username, password):
|
def login(client, username, password):
|
||||||
|
|
@ -44,25 +63,25 @@ def test_empty_db(client):
|
||||||
assert b'No entries here so far' in rv.data
|
assert b'No entries here so far' in rv.data
|
||||||
|
|
||||||
|
|
||||||
def test_login_logout(client):
|
def test_login_logout(client, app):
|
||||||
"""Make sure login and logout works"""
|
"""Make sure login and logout works"""
|
||||||
rv = login(client, flaskr.app.config['USERNAME'],
|
rv = login(client, app.config['USERNAME'],
|
||||||
flaskr.app.config['PASSWORD'])
|
app.config['PASSWORD'])
|
||||||
assert b'You were logged in' in rv.data
|
assert b'You were logged in' in rv.data
|
||||||
rv = logout(client)
|
rv = logout(client)
|
||||||
assert b'You were logged out' in rv.data
|
assert b'You were logged out' in rv.data
|
||||||
rv = login(client, flaskr.app.config['USERNAME'] + 'x',
|
rv = login(client,app.config['USERNAME'] + 'x',
|
||||||
flaskr.app.config['PASSWORD'])
|
app.config['PASSWORD'])
|
||||||
assert b'Invalid username' in rv.data
|
assert b'Invalid username' in rv.data
|
||||||
rv = login(client, flaskr.app.config['USERNAME'],
|
rv = login(client, app.config['USERNAME'],
|
||||||
flaskr.app.config['PASSWORD'] + 'x')
|
app.config['PASSWORD'] + 'x')
|
||||||
assert b'Invalid password' in rv.data
|
assert b'Invalid password' in rv.data
|
||||||
|
|
||||||
|
|
||||||
def test_messages(client):
|
def test_messages(client, app):
|
||||||
"""Test that messages work"""
|
"""Test that messages work"""
|
||||||
login(client, flaskr.app.config['USERNAME'],
|
login(client, app.config['USERNAME'],
|
||||||
flaskr.app.config['PASSWORD'])
|
app.config['PASSWORD'])
|
||||||
rv = client.post('/add', data=dict(
|
rv = client.post('/add', data=dict(
|
||||||
title='<Hello>',
|
title='<Hello>',
|
||||||
text='<strong>HTML</strong> allowed here'
|
text='<strong>HTML</strong> allowed here'
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ DEBUG = True
|
||||||
SECRET_KEY = 'development key'
|
SECRET_KEY = 'development key'
|
||||||
|
|
||||||
# create our little application :)
|
# create our little application :)
|
||||||
app = Flask(__name__)
|
app = Flask('minitwit')
|
||||||
app.config.from_object(__name__)
|
app.config.from_object(__name__)
|
||||||
app.config.from_envvar('MINITWIT_SETTINGS', silent=True)
|
app.config.from_envvar('MINITWIT_SETTINGS', silent=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
app = Flask(__name__)
|
app = Flask('yourapplication')
|
||||||
|
|
||||||
import yourapplication.views
|
import yourapplication.views
|
||||||
111
flask/app.py
111
flask/app.py
|
|
@ -10,6 +10,7 @@
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
@ -17,7 +18,8 @@ from threading import Lock
|
||||||
|
|
||||||
from werkzeug.datastructures import ImmutableDict, Headers
|
from werkzeug.datastructures import ImmutableDict, Headers
|
||||||
from werkzeug.exceptions import BadRequest, HTTPException, \
|
from werkzeug.exceptions import BadRequest, HTTPException, \
|
||||||
InternalServerError, MethodNotAllowed, default_exceptions
|
InternalServerError, MethodNotAllowed, default_exceptions, \
|
||||||
|
BadRequestKeyError
|
||||||
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
||||||
|
|
||||||
from . import cli, json
|
from . import cli, json
|
||||||
|
|
@ -307,7 +309,7 @@ class Flask(_PackageBoundObject):
|
||||||
'LOGGER_NAME': None,
|
'LOGGER_NAME': None,
|
||||||
'LOGGER_HANDLER_POLICY': 'always',
|
'LOGGER_HANDLER_POLICY': 'always',
|
||||||
'SERVER_NAME': None,
|
'SERVER_NAME': None,
|
||||||
'APPLICATION_ROOT': None,
|
'APPLICATION_ROOT': '/',
|
||||||
'SESSION_COOKIE_NAME': 'session',
|
'SESSION_COOKIE_NAME': 'session',
|
||||||
'SESSION_COOKIE_DOMAIN': None,
|
'SESSION_COOKIE_DOMAIN': None,
|
||||||
'SESSION_COOKIE_PATH': None,
|
'SESSION_COOKIE_PATH': None,
|
||||||
|
|
@ -316,7 +318,7 @@ class Flask(_PackageBoundObject):
|
||||||
'SESSION_REFRESH_EACH_REQUEST': True,
|
'SESSION_REFRESH_EACH_REQUEST': True,
|
||||||
'MAX_CONTENT_LENGTH': None,
|
'MAX_CONTENT_LENGTH': None,
|
||||||
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
|
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
|
||||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
'TRAP_BAD_REQUEST_ERRORS': None,
|
||||||
'TRAP_HTTP_EXCEPTIONS': False,
|
'TRAP_HTTP_EXCEPTIONS': False,
|
||||||
'EXPLAIN_TEMPLATE_LOADING': False,
|
'EXPLAIN_TEMPLATE_LOADING': False,
|
||||||
'PREFERRED_URL_SCHEME': 'http',
|
'PREFERRED_URL_SCHEME': 'http',
|
||||||
|
|
@ -925,8 +927,17 @@ class Flask(_PackageBoundObject):
|
||||||
:attr:`secret_key` is set. Instead of overriding this method
|
:attr:`secret_key` is set. Instead of overriding this method
|
||||||
we recommend replacing the :class:`session_interface`.
|
we recommend replacing the :class:`session_interface`.
|
||||||
|
|
||||||
|
.. deprecated: 1.0
|
||||||
|
Will be removed in 1.1. Use ``session_interface.open_session``
|
||||||
|
instead.
|
||||||
|
|
||||||
:param request: an instance of :attr:`request_class`.
|
:param request: an instance of :attr:`request_class`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
warnings.warn(DeprecationWarning(
|
||||||
|
'"open_session" is deprecated and will be removed in 1.1. Use'
|
||||||
|
' "session_interface.open_session" instead.'
|
||||||
|
))
|
||||||
return self.session_interface.open_session(self, request)
|
return self.session_interface.open_session(self, request)
|
||||||
|
|
||||||
def save_session(self, session, response):
|
def save_session(self, session, response):
|
||||||
|
|
@ -934,19 +945,37 @@ class Flask(_PackageBoundObject):
|
||||||
implementation, check :meth:`open_session`. Instead of overriding this
|
implementation, check :meth:`open_session`. Instead of overriding this
|
||||||
method we recommend replacing the :class:`session_interface`.
|
method we recommend replacing the :class:`session_interface`.
|
||||||
|
|
||||||
|
.. deprecated: 1.0
|
||||||
|
Will be removed in 1.1. Use ``session_interface.save_session``
|
||||||
|
instead.
|
||||||
|
|
||||||
:param session: the session to be saved (a
|
:param session: the session to be saved (a
|
||||||
:class:`~werkzeug.contrib.securecookie.SecureCookie`
|
:class:`~werkzeug.contrib.securecookie.SecureCookie`
|
||||||
object)
|
object)
|
||||||
:param response: an instance of :attr:`response_class`
|
:param response: an instance of :attr:`response_class`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
warnings.warn(DeprecationWarning(
|
||||||
|
'"save_session" is deprecated and will be removed in 1.1. Use'
|
||||||
|
' "session_interface.save_session" instead.'
|
||||||
|
))
|
||||||
return self.session_interface.save_session(self, session, response)
|
return self.session_interface.save_session(self, session, response)
|
||||||
|
|
||||||
def make_null_session(self):
|
def make_null_session(self):
|
||||||
"""Creates a new instance of a missing session. Instead of overriding
|
"""Creates a new instance of a missing session. Instead of overriding
|
||||||
this method we recommend replacing the :class:`session_interface`.
|
this method we recommend replacing the :class:`session_interface`.
|
||||||
|
|
||||||
|
.. deprecated: 1.0
|
||||||
|
Will be removed in 1.1. Use ``session_interface.make_null_session``
|
||||||
|
instead.
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
warnings.warn(DeprecationWarning(
|
||||||
|
'"make_null_session" is deprecated and will be removed in 1.1. Use'
|
||||||
|
' "session_interface.make_null_session" instead.'
|
||||||
|
))
|
||||||
return self.session_interface.make_null_session(self)
|
return self.session_interface.make_null_session(self)
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
|
|
@ -1137,7 +1166,9 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def errorhandler(self, code_or_exception):
|
def errorhandler(self, code_or_exception):
|
||||||
"""A decorator that is used to register a function given an
|
"""Register a function to handle errors by code or exception class.
|
||||||
|
|
||||||
|
A decorator that is used to register a function given an
|
||||||
error code. Example::
|
error code. Example::
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
|
|
@ -1150,21 +1181,6 @@ class Flask(_PackageBoundObject):
|
||||||
def special_exception_handler(error):
|
def special_exception_handler(error):
|
||||||
return 'Database connection failed', 500
|
return 'Database connection failed', 500
|
||||||
|
|
||||||
You can also register a function as error handler without using
|
|
||||||
the :meth:`errorhandler` decorator. The following example is
|
|
||||||
equivalent to the one above::
|
|
||||||
|
|
||||||
def page_not_found(error):
|
|
||||||
return 'This page does not exist', 404
|
|
||||||
app.error_handler_spec[None][404] = page_not_found
|
|
||||||
|
|
||||||
Setting error handlers via assignments to :attr:`error_handler_spec`
|
|
||||||
however is discouraged as it requires fiddling with nested dictionaries
|
|
||||||
and the special case for arbitrary exception types.
|
|
||||||
|
|
||||||
The first ``None`` refers to the active blueprint. If the error
|
|
||||||
handler should be application wide ``None`` shall be used.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
Use :meth:`register_error_handler` instead of modifying
|
Use :meth:`register_error_handler` instead of modifying
|
||||||
:attr:`error_handler_spec` directly, for application wide error
|
:attr:`error_handler_spec` directly, for application wide error
|
||||||
|
|
@ -1183,6 +1199,7 @@ class Flask(_PackageBoundObject):
|
||||||
return f
|
return f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@setupmethod
|
||||||
def register_error_handler(self, code_or_exception, f):
|
def register_error_handler(self, code_or_exception, f):
|
||||||
"""Alternative error attach function to the :meth:`errorhandler`
|
"""Alternative error attach function to the :meth:`errorhandler`
|
||||||
decorator that is more straightforward to use for non decorator
|
decorator that is more straightforward to use for non decorator
|
||||||
|
|
@ -1201,11 +1218,18 @@ class Flask(_PackageBoundObject):
|
||||||
"""
|
"""
|
||||||
if isinstance(code_or_exception, HTTPException): # old broken behavior
|
if isinstance(code_or_exception, HTTPException): # old broken behavior
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Tried to register a handler for an exception instance {0!r}. '
|
'Tried to register a handler for an exception instance {0!r}.'
|
||||||
'Handlers can only be registered for exception classes or HTTP error codes.'
|
' Handlers can only be registered for exception classes or'
|
||||||
.format(code_or_exception))
|
' HTTP error codes.'.format(code_or_exception)
|
||||||
|
)
|
||||||
|
|
||||||
exc_class, code = self._get_exc_class_and_code(code_or_exception)
|
try:
|
||||||
|
exc_class, code = self._get_exc_class_and_code(code_or_exception)
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(
|
||||||
|
"'{0}' is not a recognized HTTP error code. Use a subclass of"
|
||||||
|
" HTTPException with that code instead.".format(code_or_exception)
|
||||||
|
)
|
||||||
|
|
||||||
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
|
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
|
||||||
handlers[exc_class] = f
|
handlers[exc_class] = f
|
||||||
|
|
@ -1515,12 +1539,20 @@ class Flask(_PackageBoundObject):
|
||||||
traceback. This is helpful for debugging implicitly raised HTTP
|
traceback. This is helpful for debugging implicitly raised HTTP
|
||||||
exceptions.
|
exceptions.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
Bad request errors are not trapped by default in debug mode.
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
"""
|
"""
|
||||||
if self.config['TRAP_HTTP_EXCEPTIONS']:
|
if self.config['TRAP_HTTP_EXCEPTIONS']:
|
||||||
return True
|
return True
|
||||||
if self.config['TRAP_BAD_REQUEST_ERRORS']:
|
|
||||||
|
trap_bad_request = self.config['TRAP_BAD_REQUEST_ERRORS']
|
||||||
|
|
||||||
|
# if unset, trap based on debug mode
|
||||||
|
if (trap_bad_request is None and self.debug) or trap_bad_request:
|
||||||
return isinstance(e, BadRequest)
|
return isinstance(e, BadRequest)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_user_exception(self, e):
|
def handle_user_exception(self, e):
|
||||||
|
|
@ -1531,16 +1563,30 @@ class Flask(_PackageBoundObject):
|
||||||
function will either return a response value or reraise the
|
function will either return a response value or reraise the
|
||||||
exception with the same traceback.
|
exception with the same traceback.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
Key errors raised from request data like ``form`` show the the bad
|
||||||
|
key in debug mode rather than a generic bad request message.
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
exc_type, exc_value, tb = sys.exc_info()
|
||||||
assert exc_value is e
|
assert exc_value is e
|
||||||
|
|
||||||
# ensure not to trash sys.exc_info() at that point in case someone
|
# ensure not to trash sys.exc_info() at that point in case someone
|
||||||
# wants the traceback preserved in handle_http_exception. Of course
|
# wants the traceback preserved in handle_http_exception. Of course
|
||||||
# we cannot prevent users from trashing it themselves in a custom
|
# we cannot prevent users from trashing it themselves in a custom
|
||||||
# trap_http_exception method so that's their fault then.
|
# trap_http_exception method so that's their fault then.
|
||||||
|
|
||||||
|
# MultiDict passes the key to the exception, but that's ignored
|
||||||
|
# when generating the response message. Set an informative
|
||||||
|
# description for key errors in debug mode or when trapping errors.
|
||||||
|
if (
|
||||||
|
(self.debug or self.config['TRAP_BAD_REQUEST_ERRORS'])
|
||||||
|
and isinstance(e, BadRequestKeyError)
|
||||||
|
# only set it if it's still the default description
|
||||||
|
and e.description is BadRequestKeyError.description
|
||||||
|
):
|
||||||
|
e.description = "KeyError: '{0}'".format(*e.args)
|
||||||
|
|
||||||
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
|
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
|
||||||
return self.handle_http_exception(e)
|
return self.handle_http_exception(e)
|
||||||
|
|
||||||
|
|
@ -1845,7 +1891,7 @@ class Flask(_PackageBoundObject):
|
||||||
if self.config['SERVER_NAME'] is not None:
|
if self.config['SERVER_NAME'] is not None:
|
||||||
return self.url_map.bind(
|
return self.url_map.bind(
|
||||||
self.config['SERVER_NAME'],
|
self.config['SERVER_NAME'],
|
||||||
script_name=self.config['APPLICATION_ROOT'] or '/',
|
script_name=self.config['APPLICATION_ROOT'],
|
||||||
url_scheme=self.config['PREFERRED_URL_SCHEME'])
|
url_scheme=self.config['PREFERRED_URL_SCHEME'])
|
||||||
|
|
||||||
def inject_url_defaults(self, endpoint, values):
|
def inject_url_defaults(self, endpoint, values):
|
||||||
|
|
@ -1932,7 +1978,7 @@ class Flask(_PackageBoundObject):
|
||||||
for handler in funcs:
|
for handler in funcs:
|
||||||
response = handler(response)
|
response = handler(response)
|
||||||
if not self.session_interface.is_null_session(ctx.session):
|
if not self.session_interface.is_null_session(ctx.session):
|
||||||
self.save_session(ctx.session, response)
|
self.session_interface.save_session(self, ctx.session, response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def do_teardown_request(self, exc=_sentinel):
|
def do_teardown_request(self, exc=_sentinel):
|
||||||
|
|
@ -2017,10 +2063,19 @@ class Flask(_PackageBoundObject):
|
||||||
def test_request_context(self, *args, **kwargs):
|
def test_request_context(self, *args, **kwargs):
|
||||||
"""Creates a WSGI environment from the given values (see
|
"""Creates a WSGI environment from the given values (see
|
||||||
:class:`werkzeug.test.EnvironBuilder` for more information, this
|
:class:`werkzeug.test.EnvironBuilder` for more information, this
|
||||||
function accepts the same arguments).
|
function accepts the same arguments plus two additional).
|
||||||
|
|
||||||
|
Additional arguments (only if ``base_url`` is not specified):
|
||||||
|
|
||||||
|
:param subdomain: subdomain to use for route matching
|
||||||
|
:param url_scheme: scheme for the request, default
|
||||||
|
``PREFERRED_URL_SCHEME`` or ``http``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask.testing import make_test_environ_builder
|
from flask.testing import make_test_environ_builder
|
||||||
|
|
||||||
builder = make_test_environ_builder(self, *args, **kwargs)
|
builder = make_test_environ_builder(self, *args, **kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.request_context(builder.get_environ())
|
return self.request_context(builder.get_environ())
|
||||||
finally:
|
finally:
|
||||||
|
|
|
||||||
93
flask/cli.py
93
flask/cli.py
|
|
@ -9,7 +9,10 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
@ -55,20 +58,20 @@ def find_best_app(script_info, module):
|
||||||
' one.'.format(module=module.__name__)
|
' one.'.format(module=module.__name__)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search for app factory callables.
|
# Search for app factory functions.
|
||||||
for attr_name in ('create_app', 'make_app'):
|
for attr_name in ('create_app', 'make_app'):
|
||||||
app_factory = getattr(module, attr_name, None)
|
app_factory = getattr(module, attr_name, None)
|
||||||
|
|
||||||
if callable(app_factory):
|
if inspect.isfunction(app_factory):
|
||||||
try:
|
try:
|
||||||
app = call_factory(app_factory, script_info)
|
app = call_factory(app_factory, script_info)
|
||||||
if isinstance(app, Flask):
|
if isinstance(app, Flask):
|
||||||
return app
|
return app
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Auto-detected "{callable}()" in module "{module}", but '
|
'Auto-detected "{function}()" in module "{module}", but '
|
||||||
'could not call it without specifying arguments.'.format(
|
'could not call it without specifying arguments.'.format(
|
||||||
callable=attr_name, module=module.__name__
|
function=attr_name, module=module.__name__
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -79,18 +82,69 @@ def find_best_app(script_info, module):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def call_factory(func, script_info):
|
def call_factory(app_factory, script_info, arguments=()):
|
||||||
"""Checks if the given app factory function has an argument named
|
"""Takes an app factory, a ``script_info` object and optionally a tuple
|
||||||
``script_info`` or just a single argument and calls the function passing
|
of arguments. Checks for the existence of a script_info argument and calls
|
||||||
``script_info`` if so. Otherwise, calls the function without any arguments
|
the app_factory depending on that and the arguments provided.
|
||||||
and returns the result.
|
|
||||||
"""
|
"""
|
||||||
arguments = getargspec(func).args
|
args_spec = getargspec(app_factory)
|
||||||
if 'script_info' in arguments:
|
arg_names = args_spec.args
|
||||||
return func(script_info=script_info)
|
arg_defaults = args_spec.defaults
|
||||||
elif len(arguments) == 1:
|
|
||||||
return func(script_info)
|
if 'script_info' in arg_names:
|
||||||
return func()
|
return app_factory(*arguments, script_info=script_info)
|
||||||
|
elif arguments:
|
||||||
|
return app_factory(*arguments)
|
||||||
|
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
|
||||||
|
return app_factory(script_info)
|
||||||
|
return app_factory()
|
||||||
|
|
||||||
|
|
||||||
|
def find_app_by_string(string, script_info, module):
|
||||||
|
"""Checks if the given string is a variable name or a function. If it is
|
||||||
|
a function, it checks for specified arguments and whether it takes
|
||||||
|
a ``script_info`` argument and calls the function with the appropriate
|
||||||
|
arguments."""
|
||||||
|
from . import Flask
|
||||||
|
function_regex = r'^(?P<name>\w+)(?:\((?P<args>.*)\))?$'
|
||||||
|
match = re.match(function_regex, string)
|
||||||
|
if match:
|
||||||
|
name, args = match.groups()
|
||||||
|
try:
|
||||||
|
if args is not None:
|
||||||
|
args = args.rstrip(' ,')
|
||||||
|
if args:
|
||||||
|
args = ast.literal_eval(
|
||||||
|
"({args}, )".format(args=args))
|
||||||
|
else:
|
||||||
|
args = ()
|
||||||
|
app_factory = getattr(module, name, None)
|
||||||
|
app = call_factory(app_factory, script_info, args)
|
||||||
|
else:
|
||||||
|
attr = getattr(module, name, None)
|
||||||
|
if inspect.isfunction(attr):
|
||||||
|
app = call_factory(attr, script_info)
|
||||||
|
else:
|
||||||
|
app = attr
|
||||||
|
|
||||||
|
if isinstance(app, Flask):
|
||||||
|
return app
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Failed to find application in module '
|
||||||
|
'"{name}"'.format(name=module))
|
||||||
|
except TypeError as e:
|
||||||
|
new_error = NoAppException(
|
||||||
|
'{e}\nThe app factory "{factory}" in module "{module}" could'
|
||||||
|
' not be called with the specified arguments (and a'
|
||||||
|
' script_info argument automatically added if applicable).'
|
||||||
|
' Did you make sure to use the right number of arguments as'
|
||||||
|
' well as not using keyword arguments or'
|
||||||
|
' non-literals?'.format(e=e, factory=string, module=module))
|
||||||
|
reraise(NoAppException, new_error, sys.exc_info()[2])
|
||||||
|
else:
|
||||||
|
raise NoAppException(
|
||||||
|
'The provided string "{string}" is not a valid variable name'
|
||||||
|
'or function expression.'.format(string=string))
|
||||||
|
|
||||||
|
|
||||||
def prepare_exec_for_file(filename):
|
def prepare_exec_for_file(filename):
|
||||||
|
|
@ -148,14 +202,9 @@ def locate_app(script_info, app_id):
|
||||||
|
|
||||||
mod = sys.modules[module]
|
mod = sys.modules[module]
|
||||||
if app_obj is None:
|
if app_obj is None:
|
||||||
app = find_best_app(script_info, mod)
|
return find_best_app(script_info, mod)
|
||||||
else:
|
else:
|
||||||
app = getattr(mod, app_obj, None)
|
return find_app_by_string(app_obj, script_info, mod)
|
||||||
if app is None:
|
|
||||||
raise RuntimeError('Failed to find application in module "%s"'
|
|
||||||
% module)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
def find_default_import_path():
|
def find_default_import_path():
|
||||||
|
|
|
||||||
|
|
@ -329,9 +329,11 @@ class RequestContext(object):
|
||||||
# available. This allows a custom open_session method to use the
|
# available. This allows a custom open_session method to use the
|
||||||
# request context (e.g. code that access database information
|
# request context (e.g. code that access database information
|
||||||
# stored on `g` instead of the appcontext).
|
# stored on `g` instead of the appcontext).
|
||||||
self.session = self.app.open_session(self.request)
|
session_interface = self.app.session_interface
|
||||||
|
self.session = session_interface.open_session(self.app, self.request)
|
||||||
|
|
||||||
if self.session is None:
|
if self.session is None:
|
||||||
self.session = self.app.make_null_session()
|
self.session = session_interface.make_null_session(self.app)
|
||||||
|
|
||||||
def pop(self, exc=_sentinel):
|
def pop(self, exc=_sentinel):
|
||||||
"""Pops the request context and unbinds it by doing that. This will
|
"""Pops the request context and unbinds it by doing that. This will
|
||||||
|
|
|
||||||
|
|
@ -1004,17 +1004,3 @@ def is_ip(value):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def patch_vary_header(response, value):
|
|
||||||
"""Add a value to the ``Vary`` header if it is not already present."""
|
|
||||||
|
|
||||||
header = response.headers.get('Vary', '')
|
|
||||||
headers = [h for h in (h.strip() for h in header.split(',')) if h]
|
|
||||||
lower_value = value.lower()
|
|
||||||
|
|
||||||
if not any(h.lower() == lower_value for h in headers):
|
|
||||||
headers.append(value)
|
|
||||||
|
|
||||||
updated_header = ', '.join(headers)
|
|
||||||
response.headers['Vary'] = updated_header
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ from itsdangerous import BadSignature, URLSafeTimedSerializer
|
||||||
from werkzeug.datastructures import CallbackDict
|
from werkzeug.datastructures import CallbackDict
|
||||||
from werkzeug.http import http_date, parse_date
|
from werkzeug.http import http_date, parse_date
|
||||||
|
|
||||||
from flask.helpers import patch_vary_header
|
|
||||||
from . import Markup, json
|
from . import Markup, json
|
||||||
from ._compat import iteritems, text_type
|
from ._compat import iteritems, text_type
|
||||||
from .helpers import is_ip, total_seconds
|
from .helpers import is_ip, total_seconds
|
||||||
|
|
@ -347,8 +346,8 @@ class SessionInterface(object):
|
||||||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
||||||
uses ``/`` if it's ``None``.
|
uses ``/`` if it's ``None``.
|
||||||
"""
|
"""
|
||||||
return app.config['SESSION_COOKIE_PATH'] or \
|
return app.config['SESSION_COOKIE_PATH'] \
|
||||||
app.config['APPLICATION_ROOT'] or '/'
|
or app.config['APPLICATION_ROOT']
|
||||||
|
|
||||||
def get_cookie_httponly(self, app):
|
def get_cookie_httponly(self, app):
|
||||||
"""Returns True if the session cookie should be httponly. This
|
"""Returns True if the session cookie should be httponly. This
|
||||||
|
|
@ -466,7 +465,7 @@ class SecureCookieSessionInterface(SessionInterface):
|
||||||
|
|
||||||
# Add a "Vary: Cookie" header if the session was accessed at all.
|
# Add a "Vary: Cookie" header if the session was accessed at all.
|
||||||
if session.accessed:
|
if session.accessed:
|
||||||
patch_vary_header(response, 'Cookie')
|
response.vary.add('Cookie')
|
||||||
|
|
||||||
if not self.should_set_cookie(app, session):
|
if not self.should_set_cookie(app, session):
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,37 @@ except ImportError:
|
||||||
from urlparse import urlsplit as url_parse
|
from urlparse import urlsplit as url_parse
|
||||||
|
|
||||||
|
|
||||||
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
|
def make_test_environ_builder(
|
||||||
|
app, path='/', base_url=None, subdomain=None, url_scheme=None,
|
||||||
|
*args, **kwargs
|
||||||
|
):
|
||||||
"""Creates a new test builder with some application defaults thrown in."""
|
"""Creates a new test builder with some application defaults thrown in."""
|
||||||
http_host = app.config.get('SERVER_NAME')
|
|
||||||
app_root = app.config.get('APPLICATION_ROOT')
|
assert (
|
||||||
|
not (base_url or subdomain or url_scheme)
|
||||||
|
or (base_url is not None) != bool(subdomain or url_scheme)
|
||||||
|
), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
|
||||||
|
|
||||||
if base_url is None:
|
if base_url is None:
|
||||||
|
http_host = app.config.get('SERVER_NAME') or 'localhost'
|
||||||
|
app_root = app.config['APPLICATION_ROOT']
|
||||||
|
|
||||||
|
if subdomain:
|
||||||
|
http_host = '{0}.{1}'.format(subdomain, http_host)
|
||||||
|
|
||||||
|
if url_scheme is None:
|
||||||
|
url_scheme = app.config['PREFERRED_URL_SCHEME']
|
||||||
|
|
||||||
url = url_parse(path)
|
url = url_parse(path)
|
||||||
base_url = 'http://%s/' % (url.netloc or http_host or 'localhost')
|
base_url = '{0}://{1}/{2}'.format(
|
||||||
if app_root:
|
url_scheme, url.netloc or http_host, app_root.lstrip('/')
|
||||||
base_url += app_root.lstrip('/')
|
)
|
||||||
if url.netloc:
|
path = url.path
|
||||||
path = url.path
|
|
||||||
if url.query:
|
if url.query:
|
||||||
path += '?' + url.query
|
sep = b'?' if isinstance(url.query, bytes) else '?'
|
||||||
|
path += sep + url.query
|
||||||
|
|
||||||
return EnvironBuilder(path, base_url, *args, **kwargs)
|
return EnvironBuilder(path, base_url, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -87,7 +105,8 @@ class FlaskClient(Client):
|
||||||
self.cookie_jar.inject_wsgi(environ_overrides)
|
self.cookie_jar.inject_wsgi(environ_overrides)
|
||||||
outer_reqctx = _request_ctx_stack.top
|
outer_reqctx = _request_ctx_stack.top
|
||||||
with app.test_request_context(*args, **kwargs) as c:
|
with app.test_request_context(*args, **kwargs) as c:
|
||||||
sess = app.open_session(c.request)
|
session_interface = app.session_interface
|
||||||
|
sess = session_interface.open_session(app, c.request)
|
||||||
if sess is None:
|
if sess is None:
|
||||||
raise RuntimeError('Session backend did not open a session. '
|
raise RuntimeError('Session backend did not open a session. '
|
||||||
'Check the configuration')
|
'Check the configuration')
|
||||||
|
|
@ -106,8 +125,8 @@ class FlaskClient(Client):
|
||||||
_request_ctx_stack.pop()
|
_request_ctx_stack.pop()
|
||||||
|
|
||||||
resp = app.response_class()
|
resp = app.response_class()
|
||||||
if not app.session_interface.is_null_session(sess):
|
if not session_interface.is_null_session(sess):
|
||||||
app.save_session(sess, resp)
|
session_interface.save_session(app, sess, resp)
|
||||||
headers = resp.get_wsgi_headers(c.request.environ)
|
headers = resp.get_wsgi_headers(c.request.environ)
|
||||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
||||||
|
|
||||||
|
|
|
||||||
14
setup.py
14
setup.py
|
|
@ -48,14 +48,12 @@ import re
|
||||||
import ast
|
import ast
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
||||||
|
|
||||||
with open('flask/__init__.py', 'rb') as f:
|
with open('flask/__init__.py', 'rb') as f:
|
||||||
version = str(ast.literal_eval(_version_re.search(
|
version = str(ast.literal_eval(_version_re.search(
|
||||||
f.read().decode('utf-8')).group(1)))
|
f.read().decode('utf-8')).group(1)))
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Flask',
|
name='Flask',
|
||||||
version=version,
|
version=version,
|
||||||
|
|
@ -76,6 +74,17 @@ setup(
|
||||||
'itsdangerous>=0.21',
|
'itsdangerous>=0.21',
|
||||||
'click>=4.0',
|
'click>=4.0',
|
||||||
],
|
],
|
||||||
|
extras_require={
|
||||||
|
'dev': [
|
||||||
|
'blinker',
|
||||||
|
'greenlet',
|
||||||
|
'pytest>=3',
|
||||||
|
'coverage',
|
||||||
|
'tox',
|
||||||
|
'sphinx',
|
||||||
|
'sphinxcontrib-log-cabinet'
|
||||||
|
],
|
||||||
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
'Environment :: Web Environment',
|
'Environment :: Web Environment',
|
||||||
|
|
@ -90,6 +99,7 @@ setup(
|
||||||
'Programming Language :: Python :: 3.3',
|
'Programming Language :: Python :: 3.3',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.4',
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||||
],
|
],
|
||||||
|
|
|
||||||
15
tests/test_apps/cliapp/factory.py
Normal file
15
tests/test_apps/cliapp/factory.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
return Flask('create_app')
|
||||||
|
|
||||||
|
|
||||||
|
def create_app2(foo, bar):
|
||||||
|
return Flask("_".join(['create_app2', foo, bar]))
|
||||||
|
|
||||||
|
|
||||||
|
def create_app3(foo, bar, script_info):
|
||||||
|
return Flask("_".join(['create_app3', foo, bar]))
|
||||||
|
|
@ -9,20 +9,21 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import re
|
|
||||||
import uuid
|
|
||||||
import time
|
|
||||||
import flask
|
|
||||||
import pickle
|
import pickle
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from flask._compat import text_type
|
|
||||||
from werkzeug.exceptions import BadRequest, NotFound, Forbidden
|
import pytest
|
||||||
|
import werkzeug.serving
|
||||||
|
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
||||||
from werkzeug.http import parse_date
|
from werkzeug.http import parse_date
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
import werkzeug.serving
|
|
||||||
|
import flask
|
||||||
|
from flask._compat import text_type
|
||||||
|
|
||||||
|
|
||||||
def test_options_work(app, client):
|
def test_options_work(app, client):
|
||||||
|
|
@ -529,14 +530,14 @@ def test_session_vary_cookie(app, client):
|
||||||
@app.route('/vary-cookie-header-set')
|
@app.route('/vary-cookie-header-set')
|
||||||
def vary_cookie_header_set():
|
def vary_cookie_header_set():
|
||||||
response = flask.Response()
|
response = flask.Response()
|
||||||
response.headers['Vary'] = 'Cookie'
|
response.vary.add('Cookie')
|
||||||
flask.session['test'] = 'test'
|
flask.session['test'] = 'test'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.route('/vary-header-set')
|
@app.route('/vary-header-set')
|
||||||
def vary_header_set():
|
def vary_header_set():
|
||||||
response = flask.Response()
|
response = flask.Response()
|
||||||
response.headers['Vary'] = 'Accept-Encoding, Accept-Language'
|
response.vary.update(('Accept-Encoding', 'Accept-Language'))
|
||||||
flask.session['test'] = 'test'
|
flask.session['test'] = 'test'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
@ -875,6 +876,13 @@ def test_error_handling(app, client):
|
||||||
assert b'forbidden' == rv.data
|
assert b'forbidden' == rv.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_handler_unknown_code(app):
|
||||||
|
with pytest.raises(KeyError) as exc_info:
|
||||||
|
app.register_error_handler(999, lambda e: ('999', 999))
|
||||||
|
|
||||||
|
assert 'Use a subclass' in exc_info.value.args[0]
|
||||||
|
|
||||||
|
|
||||||
def test_error_handling_processing(app, client):
|
def test_error_handling_processing(app, client):
|
||||||
app.config['LOGGER_HANDLER_POLICY'] = 'never'
|
app.config['LOGGER_HANDLER_POLICY'] = 'never'
|
||||||
app.testing = False
|
app.testing = False
|
||||||
|
|
@ -980,12 +988,17 @@ def test_trapping_of_bad_request_key_errors(app, client):
|
||||||
def fail():
|
def fail():
|
||||||
flask.request.form['missing_key']
|
flask.request.form['missing_key']
|
||||||
|
|
||||||
assert client.get('/fail').status_code == 400
|
rv = client.get('/fail')
|
||||||
|
assert rv.status_code == 400
|
||||||
|
assert b'missing_key' not in rv.data
|
||||||
|
|
||||||
app.config['TRAP_BAD_REQUEST_ERRORS'] = True
|
app.config['TRAP_BAD_REQUEST_ERRORS'] = True
|
||||||
|
|
||||||
with pytest.raises(KeyError) as e:
|
with pytest.raises(KeyError) as e:
|
||||||
client.get("/fail")
|
client.get("/fail")
|
||||||
|
|
||||||
assert e.errisinstance(BadRequest)
|
assert e.errisinstance(BadRequest)
|
||||||
|
assert 'missing_key' in e.value.description
|
||||||
|
|
||||||
|
|
||||||
def test_trapping_of_all_http_exceptions(app, client):
|
def test_trapping_of_all_http_exceptions(app, client):
|
||||||
|
|
|
||||||
|
|
@ -150,15 +150,37 @@ def test_locate_app(test_apps):
|
||||||
script_info = ScriptInfo()
|
script_info = ScriptInfo()
|
||||||
assert locate_app(script_info, "cliapp.app").name == "testapp"
|
assert locate_app(script_info, "cliapp.app").name == "testapp"
|
||||||
assert locate_app(script_info, "cliapp.app:testapp").name == "testapp"
|
assert locate_app(script_info, "cliapp.app:testapp").name == "testapp"
|
||||||
|
assert locate_app(script_info, "cliapp.factory").name == "create_app"
|
||||||
|
assert locate_app(
|
||||||
|
script_info, "cliapp.factory").name == "create_app"
|
||||||
|
assert locate_app(
|
||||||
|
script_info, "cliapp.factory:create_app").name == "create_app"
|
||||||
|
assert locate_app(
|
||||||
|
script_info, "cliapp.factory:create_app()").name == "create_app"
|
||||||
|
assert locate_app(
|
||||||
|
script_info, "cliapp.factory:create_app2('foo', 'bar')"
|
||||||
|
).name == "create_app2_foo_bar"
|
||||||
|
assert locate_app(
|
||||||
|
script_info, "cliapp.factory:create_app2('foo', 'bar', )"
|
||||||
|
).name == "create_app2_foo_bar"
|
||||||
|
assert locate_app(
|
||||||
|
script_info, "cliapp.factory:create_app3('baz', 'qux')"
|
||||||
|
).name == "create_app3_baz_qux"
|
||||||
assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1"
|
assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1"
|
||||||
pytest.raises(NoAppException, locate_app,
|
pytest.raises(
|
||||||
script_info, "notanpp.py")
|
NoAppException, locate_app, script_info, "notanpp.py")
|
||||||
pytest.raises(NoAppException, locate_app,
|
pytest.raises(
|
||||||
script_info, "cliapp/app")
|
NoAppException, locate_app, script_info, "cliapp/app")
|
||||||
pytest.raises(RuntimeError, locate_app,
|
pytest.raises(
|
||||||
script_info, "cliapp.app:notanapp")
|
RuntimeError, locate_app, script_info, "cliapp.app:notanapp")
|
||||||
pytest.raises(NoAppException, locate_app,
|
pytest.raises(
|
||||||
script_info, "cliapp.importerrorapp")
|
NoAppException, locate_app,
|
||||||
|
script_info, "cliapp.factory:create_app2('foo')")
|
||||||
|
pytest.raises(
|
||||||
|
NoAppException, locate_app,
|
||||||
|
script_info, "cliapp.factory:create_app ()")
|
||||||
|
pytest.raises(
|
||||||
|
NoAppException, locate_app, script_info, "cliapp.importerrorapp")
|
||||||
|
|
||||||
|
|
||||||
def test_find_default_import_path(test_apps, monkeypatch, tmpdir):
|
def test_find_default_import_path(test_apps, monkeypatch, tmpdir):
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class TestJSON(object):
|
||||||
|
|
||||||
def test_post_empty_json_adds_exception_to_response_content_in_debug(self, app, client):
|
def test_post_empty_json_adds_exception_to_response_content_in_debug(self, app, client):
|
||||||
app.config['DEBUG'] = True
|
app.config['DEBUG'] = True
|
||||||
|
app.config['TRAP_BAD_REQUEST_ERRORS'] = False
|
||||||
|
|
||||||
@app.route('/json', methods=['POST'])
|
@app.route('/json', methods=['POST'])
|
||||||
def post_json():
|
def post_json():
|
||||||
|
|
@ -56,6 +57,7 @@ class TestJSON(object):
|
||||||
|
|
||||||
def test_post_empty_json_wont_add_exception_to_response_if_no_debug(self, app, client):
|
def test_post_empty_json_wont_add_exception_to_response_if_no_debug(self, app, client):
|
||||||
app.config['DEBUG'] = False
|
app.config['DEBUG'] = False
|
||||||
|
app.config['TRAP_BAD_REQUEST_ERRORS'] = False
|
||||||
|
|
||||||
@app.route('/json', methods=['POST'])
|
@app.route('/json', methods=['POST'])
|
||||||
def post_json():
|
def post_json():
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,38 @@ def test_environ_base_modified(app, client, app_ctx):
|
||||||
assert flask.g.user_agent == 'Bar'
|
assert flask.g.user_agent == 'Bar'
|
||||||
|
|
||||||
|
|
||||||
|
def test_specify_url_scheme(app, client):
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return flask.request.url
|
||||||
|
|
||||||
|
ctx = app.test_request_context(url_scheme='https')
|
||||||
|
assert ctx.request.url == 'https://localhost/'
|
||||||
|
|
||||||
|
rv = client.get('/', url_scheme='https')
|
||||||
|
assert rv.data == b'https://localhost/'
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint_with_subdomain(app, client):
|
||||||
|
app.config['SERVER_NAME'] = 'example.com:1234'
|
||||||
|
app.config['APPLICATION_ROOT'] = '/foo'
|
||||||
|
|
||||||
|
bp = flask.Blueprint('company', __name__, subdomain='xxx')
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
def index():
|
||||||
|
return flask.request.url
|
||||||
|
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
ctx = app.test_request_context('/', subdomain='xxx')
|
||||||
|
assert ctx.request.url == 'http://xxx.example.com:1234/foo/'
|
||||||
|
assert ctx.request.blueprint == bp.name
|
||||||
|
|
||||||
|
rv = client.get('/', subdomain='xxx')
|
||||||
|
assert rv.data == b'http://xxx.example.com:1234/foo/'
|
||||||
|
|
||||||
|
|
||||||
def test_redirect_keep_session(app, client, app_ctx):
|
def test_redirect_keep_session(app, client, app_ctx):
|
||||||
app.secret_key = 'testing'
|
app.secret_key = 'testing'
|
||||||
|
|
||||||
|
|
|
||||||
17
tox.ini
17
tox.ini
|
|
@ -1,7 +1,7 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py{36,35,34,33,27,26,py}-release
|
py{36,35,34,33,27,26,py}
|
||||||
py{36,27,py}-release-simplejson
|
py{36,27,py}-simplejson
|
||||||
py{36,33,27,26,py}-devel
|
py{36,33,27,26,py}-devel
|
||||||
py{36,33,27,26,py}-lowest
|
py{36,33,27,26,py}-lowest
|
||||||
docs-html
|
docs-html
|
||||||
|
|
@ -35,12 +35,10 @@ commands =
|
||||||
pip install -e examples/patterns/largerapp -q
|
pip install -e examples/patterns/largerapp -q
|
||||||
|
|
||||||
# pytest-cov doesn't seem to play nice with -p
|
# pytest-cov doesn't seem to play nice with -p
|
||||||
coverage run -p -m pytest
|
coverage run -p -m pytest tests examples
|
||||||
|
|
||||||
[testenv:docs-html]
|
[testenv:docs-html]
|
||||||
deps =
|
deps = sphinx
|
||||||
sphinx
|
|
||||||
flask-sphinx-themes
|
|
||||||
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
|
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
|
||||||
|
|
||||||
[testenv:docs-linkcheck]
|
[testenv:docs-linkcheck]
|
||||||
|
|
@ -63,3 +61,10 @@ commands =
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage report
|
coverage report
|
||||||
codecov
|
codecov
|
||||||
|
|
||||||
|
[testenv:detox]
|
||||||
|
skip_install = true
|
||||||
|
deps = detox
|
||||||
|
commands =
|
||||||
|
detox -e py{36,35,34,33,27,26,py},py{36,27,py}-simplejson,py{36,33,27,26,py}-devel,py{36,33,27,26,py}-lowest,docs-html
|
||||||
|
tox -e coverage-report
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue