mirror of https://github.com/pallets/flask.git
Merge remote-tracking branch 'remotes/origin/master' into json-mixin
This commit is contained in:
commit
dbc70c9274
|
@ -0,0 +1,11 @@
|
||||||
|
[run]
|
||||||
|
branch = True
|
||||||
|
source =
|
||||||
|
flask
|
||||||
|
tests
|
||||||
|
|
||||||
|
[paths]
|
||||||
|
source =
|
||||||
|
flask
|
||||||
|
.tox/*/lib/python*/site-packages/flask
|
||||||
|
.tox/pypy/site-packages/flask
|
|
@ -0,0 +1 @@
|
||||||
|
CHANGES merge=union
|
|
@ -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:
|
||||||
|
|
|
@ -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.
|
||||||
|
-->
|
|
@ -11,3 +11,9 @@ _mailinglist
|
||||||
.tox
|
.tox
|
||||||
.cache/
|
.cache/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
htmlcov
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
*,cover
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "docs/_themes"]
|
|
||||||
path = docs/_themes
|
|
||||||
url = https://github.com/mitsuhiko/flask-sphinx-themes.git
|
|
59
.travis.yml
59
.travis.yml
|
@ -1,48 +1,37 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
python:
|
|
||||||
- "2.6"
|
|
||||||
- "2.7"
|
|
||||||
- "pypy"
|
|
||||||
- "3.3"
|
|
||||||
- "3.4"
|
|
||||||
- "3.5"
|
|
||||||
|
|
||||||
env:
|
|
||||||
- REQUIREMENTS=lowest
|
|
||||||
- REQUIREMENTS=lowest-simplejson
|
|
||||||
- REQUIREMENTS=release
|
|
||||||
- REQUIREMENTS=release-simplejson
|
|
||||||
- REQUIREMENTS=devel
|
|
||||||
- REQUIREMENTS=devel-simplejson
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
exclude:
|
include:
|
||||||
# Python 3 support currently does not work with lowest requirements
|
- python: 3.6
|
||||||
- python: "3.3"
|
env: TOXENV=py,codecov
|
||||||
env: REQUIREMENTS=lowest
|
- python: 3.5
|
||||||
- python: "3.3"
|
env: TOXENV=py,codecov
|
||||||
env: REQUIREMENTS=lowest-simplejson
|
- python: 3.4
|
||||||
- python: "3.4"
|
env: TOXENV=py,codecov
|
||||||
env: REQUIREMENTS=lowest
|
- python: 3.3
|
||||||
- python: "3.4"
|
env: TOXENV=py,codecov
|
||||||
env: REQUIREMENTS=lowest-simplejson
|
- python: 2.7
|
||||||
- python: "3.5"
|
env: TOXENV=py,codecov
|
||||||
env: REQUIREMENTS=lowest
|
- python: 2.6
|
||||||
- python: "3.5"
|
env: TOXENV=py,codecov
|
||||||
env: REQUIREMENTS=lowest-simplejson
|
- python: pypy
|
||||||
|
env: TOXENV=py,codecov
|
||||||
|
- python: nightly
|
||||||
|
env: TOXENV=py
|
||||||
|
- python: 3.6
|
||||||
|
env: TOXENV=docs-html
|
||||||
|
- python: 3.6
|
||||||
|
env: TOXENV=py-simplejson,codecov
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- tox -e py-$REQUIREMENTS
|
- tox
|
||||||
|
|
||||||
branches:
|
cache:
|
||||||
except:
|
- pip
|
||||||
- website
|
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -22,6 +22,7 @@ Patches and Suggestions
|
||||||
- Florent Xicluna
|
- Florent Xicluna
|
||||||
- Georg Brandl
|
- Georg Brandl
|
||||||
- Jeff Widman @jeffwidman
|
- Jeff Widman @jeffwidman
|
||||||
|
- Joshua Bronson @jab
|
||||||
- Justin Quick
|
- Justin Quick
|
||||||
- Kenneth Reitz
|
- Kenneth Reitz
|
||||||
- Keyan Pishdadian
|
- Keyan Pishdadian
|
||||||
|
|
131
CHANGES
131
CHANGES
|
@ -3,12 +3,127 @@ Flask Changelog
|
||||||
|
|
||||||
Here you can see the full list of changes between each Flask release.
|
Here you can see the full list of changes between each Flask release.
|
||||||
|
|
||||||
|
Version 0.13
|
||||||
|
------------
|
||||||
|
|
||||||
|
Major release, unreleased
|
||||||
|
|
||||||
|
- Minimum Werkzeug version bumped to 0.9, but please use the latest version.
|
||||||
|
- 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.
|
||||||
|
- Change default configuration ``JSONIFY_PRETTYPRINT_REGULAR=False``.
|
||||||
|
``jsonify()`` method returns compressed response by default, and pretty
|
||||||
|
response in debug mode. (`#2193`_)
|
||||||
|
- Change ``Flask.__init__`` to accept two new keyword arguments,
|
||||||
|
``host_matching`` and ``static_host``. This enables ``host_matching`` to be
|
||||||
|
set properly by the time the constructor adds the static route, and enables
|
||||||
|
the static route to be properly associated with the required host.
|
||||||
|
(``#1559``)
|
||||||
|
- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_)
|
||||||
|
- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``.
|
||||||
|
(`#2017`_)
|
||||||
|
- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable
|
||||||
|
adding OPTIONS method when the ``view_func`` argument is not a class.
|
||||||
|
(`#1489`_).
|
||||||
|
- ``MethodView`` can inherit method handlers from base classes. (`#1936`_)
|
||||||
|
- Errors caused while opening the session at the beginning of the request are
|
||||||
|
handled by the app's error handlers. (`#2254`_)
|
||||||
|
- Blueprints gained ``json_encoder`` and ``json_decoder`` attributes to
|
||||||
|
override the app's encoder and decoder. (`#1898`_)
|
||||||
|
- ``Flask.make_response`` raises ``TypeError`` instead of ``ValueError`` for
|
||||||
|
bad response types. The error messages have been improved to describe why the
|
||||||
|
type is invalid. (`#2256`_)
|
||||||
|
- Add ``routes`` CLI command to output routes registered on the application.
|
||||||
|
(`#2259`_)
|
||||||
|
- Show warning when session cookie domain is a bare hostname or an IP
|
||||||
|
address, as these may not behave properly in some browsers, such as Chrome.
|
||||||
|
(`#2282`_)
|
||||||
|
- Allow IP address as exact session cookie domain. (`#2282`_)
|
||||||
|
- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``.
|
||||||
|
(`#2282`_)
|
||||||
|
- Auto-detect zero-argument app factory called ``create_app`` or ``make_app``
|
||||||
|
from ``FLASK_APP``. (`#2297`_)
|
||||||
|
- Factory functions are not required to take a ``script_info`` parameter to
|
||||||
|
work with the ``flask`` command. If they take a single parameter or a
|
||||||
|
parameter named ``script_info``, the ``ScriptInfo`` object will be passed.
|
||||||
|
(`#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`_)
|
||||||
|
- Allow registering new tags with ``TaggedJSONSerializer`` to support
|
||||||
|
storing other types in the session cookie. (`#2352`_)
|
||||||
|
- Only open the session if the request has not been pushed onto the context
|
||||||
|
stack yet. This allows ``stream_with_context`` generators to access the same
|
||||||
|
session that the containing view uses. (`#2354`_)
|
||||||
|
|
||||||
|
.. _#1489: https://github.com/pallets/flask/pull/1489
|
||||||
|
.. _#1621: https://github.com/pallets/flask/pull/1621
|
||||||
|
.. _#1898: https://github.com/pallets/flask/pull/1898
|
||||||
|
.. _#1936: https://github.com/pallets/flask/pull/1936
|
||||||
|
.. _#2017: https://github.com/pallets/flask/pull/2017
|
||||||
|
.. _#2193: https://github.com/pallets/flask/pull/2193
|
||||||
|
.. _#2223: https://github.com/pallets/flask/pull/2223
|
||||||
|
.. _#2254: https://github.com/pallets/flask/pull/2254
|
||||||
|
.. _#2256: https://github.com/pallets/flask/pull/2256
|
||||||
|
.. _#2259: https://github.com/pallets/flask/pull/2259
|
||||||
|
.. _#2282: https://github.com/pallets/flask/pull/2282
|
||||||
|
.. _#2288: https://github.com/pallets/flask/pull/2288
|
||||||
|
.. _#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
|
||||||
|
.. _#2326: https://github.com/pallets/flask/pull/2326
|
||||||
|
.. _#2348: https://github.com/pallets/flask/pull/2348
|
||||||
|
.. _#2352: https://github.com/pallets/flask/pull/2352
|
||||||
|
.. _#2354: https://github.com/pallets/flask/pull/2354
|
||||||
|
|
||||||
|
Version 0.12.2
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Released on May 16 2017
|
||||||
|
|
||||||
|
- Fix a bug in `safe_join` on Windows.
|
||||||
|
|
||||||
|
Version 0.12.1
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Bugfix release, released on March 31st 2017
|
||||||
|
|
||||||
|
- Prevent `flask run` from showing a NoAppException when an ImportError occurs
|
||||||
|
within the imported application module.
|
||||||
|
- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix
|
||||||
|
``#2118``.
|
||||||
|
- Use the ``SERVER_NAME`` config if it is present as default values for
|
||||||
|
``app.run``. ``#2109``, ``#2152``
|
||||||
|
- Call `ctx.auto_pop` with the exception object instead of `None`, in the
|
||||||
|
event that a `BaseException` such as `KeyboardInterrupt` is raised in a
|
||||||
|
request handler.
|
||||||
|
|
||||||
Version 0.12
|
Version 0.12
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
Released on December 21st 2016, codename Punsch.
|
||||||
|
|
||||||
- the cli command now responds to `--version`.
|
- the cli command now responds to `--version`.
|
||||||
- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``.
|
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
|
||||||
See pull request ``#1849``.
|
has been removed, as per issue ``#104``. See pull request ``#1849``.
|
||||||
|
- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall back to
|
||||||
|
``application/octet-stream``. See pull request ``#1988``.
|
||||||
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
||||||
(pull request ``#1730``).
|
(pull request ``#1730``).
|
||||||
- Added `json` keyword argument to :meth:`flask.testing.FlaskClient.open`
|
- Added `json` keyword argument to :meth:`flask.testing.FlaskClient.open`
|
||||||
|
@ -16,6 +131,14 @@ Version 0.12
|
||||||
send JSON requests from the test client.
|
send JSON requests from the test client.
|
||||||
- Added ``is_json`` and ``get_json`` to :class:``flask.wrappers.Response``
|
- Added ``is_json`` and ``get_json`` to :class:``flask.wrappers.Response``
|
||||||
in order to make it easier to build assertions when testing JSON responses.
|
in order to make it easier to build assertions when testing JSON responses.
|
||||||
|
- Revert a behavior change that made the dev server crash instead of returning
|
||||||
|
a Internal Server Error (pull request ``#2006``).
|
||||||
|
- Correctly invoke response handlers for both regular request dispatching as
|
||||||
|
well as error handlers.
|
||||||
|
- Disable logger propagation by default for the app logger.
|
||||||
|
- Add support for range requests in ``send_file``.
|
||||||
|
- ``app.test_client`` includes preset default environment, which can now be
|
||||||
|
directly set, instead of per ``client.get``.
|
||||||
|
|
||||||
Version 0.11.2
|
Version 0.11.2
|
||||||
--------------
|
--------------
|
||||||
|
@ -99,6 +222,8 @@ Released on May 29th 2016, codename Absinthe.
|
||||||
- Don't leak exception info of already catched exceptions to context teardown
|
- Don't leak exception info of already catched exceptions to context teardown
|
||||||
handlers (pull request ``#1393``).
|
handlers (pull request ``#1393``).
|
||||||
- Allow custom Jinja environment subclasses (pull request ``#1422``).
|
- Allow custom Jinja environment subclasses (pull request ``#1422``).
|
||||||
|
- Updated extension dev guidelines.
|
||||||
|
|
||||||
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
|
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
|
||||||
- Turn on autoescape for ``flask.templating.render_template_string`` by default
|
- Turn on autoescape for ``flask.templating.render_template_string`` by default
|
||||||
(pull request ``#1515``).
|
(pull request ``#1515``).
|
||||||
|
@ -330,7 +455,7 @@ Released on September 29th 2011, codename Rakija
|
||||||
- Applications now not only have a root path where the resources and modules
|
- Applications now not only have a root path where the resources and modules
|
||||||
are located but also an instance path which is the designated place to
|
are located but also an instance path which is the designated place to
|
||||||
drop files that are modified at runtime (uploads etc.). Also this is
|
drop files that are modified at runtime (uploads etc.). Also this is
|
||||||
conceptionally only instance depending and outside version control so it's
|
conceptually only instance depending and outside version control so it's
|
||||||
the perfect place to put configuration files etc. For more information
|
the perfect place to put configuration files etc. For more information
|
||||||
see :ref:`instance-folders`.
|
see :ref:`instance-folders`.
|
||||||
- Added the ``APPLICATION_ROOT`` configuration variable.
|
- Added the ``APPLICATION_ROOT`` configuration variable.
|
||||||
|
|
181
CONTRIBUTING.rst
181
CONTRIBUTING.rst
|
@ -1,89 +1,166 @@
|
||||||
==========================
|
|
||||||
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
|
||||||
|
it would make the code uglier.
|
||||||
|
|
||||||
- Try to follow `PEP8 <http://legacy.python.org/dev/peps/pep-0008/>`_, but you
|
First time setup
|
||||||
may ignore the line-length-limit if following it would make the code uglier.
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Download and install the `latest version of git`_.
|
||||||
|
- Configure git with your `username`_ and `email`_::
|
||||||
|
|
||||||
Running the testsuite
|
git config --global user.name 'your name'
|
||||||
---------------------
|
git config --global user.email 'your email'
|
||||||
|
|
||||||
You probably want to set up a `virtualenv
|
- Make sure you have a `GitHub account`_.
|
||||||
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
- Fork Flask to your GitHub account by clicking the `Fork`_ button.
|
||||||
|
- `Clone`_ your GitHub fork locally::
|
||||||
The minimal requirement for running the testsuite is ``py.test``. You can
|
|
||||||
install it with::
|
|
||||||
|
|
||||||
pip install pytest
|
|
||||||
|
|
||||||
Clone this repository::
|
|
||||||
|
|
||||||
git clone https://github.com/pallets/flask.git
|
|
||||||
|
|
||||||
Install Flask as an editable package using the current source::
|
|
||||||
|
|
||||||
|
git clone https://github.com/{username}/flask
|
||||||
cd flask
|
cd flask
|
||||||
pip install --editable .
|
|
||||||
|
|
||||||
Then you can run the testsuite with::
|
- Add the main repository as a remote to update later::
|
||||||
|
|
||||||
py.test
|
git remote add pallets https://github.com/pallets/flask
|
||||||
|
git fetch pallets
|
||||||
|
|
||||||
With only py.test installed, a large part of the testsuite will get skipped
|
- Create a virtualenv::
|
||||||
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
|
python3 -m venv env
|
||||||
of ``pytest``. You can install it with::
|
. env/bin/activate
|
||||||
|
# or "env\Scripts\activate" on Windows
|
||||||
|
|
||||||
pip install tox
|
- Install Flask in editable mode with development dependencies::
|
||||||
|
|
||||||
The ``tox`` command will then run all tests against multiple combinations
|
pip install -e ".[dev]"
|
||||||
Python versions and dependency versions.
|
|
||||||
|
.. _GitHub account: https://github.com/join
|
||||||
|
.. _latest version of git: https://git-scm.com/downloads
|
||||||
|
.. _username: https://help.github.com/articles/setting-your-username-in-git/
|
||||||
|
.. _email: https://help.github.com/articles/setting-your-email-in-git/
|
||||||
|
.. _Fork: https://github.com/pallets/flask/pull/2305#fork-destination-box
|
||||||
|
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
|
||||||
|
|
||||||
|
Start coding
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Create a branch to identify the issue you would like to work on (e.g.
|
||||||
|
``2287-dry-test-suite``)
|
||||||
|
- Using your favorite editor, make your changes, `committing as you go`_.
|
||||||
|
- Try to follow `PEP8`_, but you may ignore the line length limit if following
|
||||||
|
it would make the code uglier.
|
||||||
|
- Include tests that cover any code changes you make. Make sure the test fails
|
||||||
|
without your patch. `Run the tests. <contributing-testsuite_>`_.
|
||||||
|
- Push your commits to GitHub and `create a pull request`_.
|
||||||
|
- Celebrate 🎉
|
||||||
|
|
||||||
|
.. _committing as you go: http://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes
|
||||||
|
.. _PEP8: https://pep8.org/
|
||||||
|
.. _create a pull request: https://help.github.com/articles/creating-a-pull-request/
|
||||||
|
|
||||||
|
.. _contributing-testsuite:
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Run the basic test suite with::
|
||||||
|
|
||||||
|
pytest
|
||||||
|
|
||||||
|
This only runs the tests for the current environment. Whether this is relevant
|
||||||
|
depends on which part of Flask you're working on. Travis-CI will run the full
|
||||||
|
suite when you submit your pull request.
|
||||||
|
|
||||||
|
The full test suite takes a long time to run because it tests multiple
|
||||||
|
combinations of Python and dependencies. You need to have Python 2.6, 2.7, 3.3,
|
||||||
|
3.4, 3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run::
|
||||||
|
|
||||||
|
tox
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
py.test --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.
|
||||||
|
|
||||||
py.test --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.
|
||||||
|
|
||||||
|
- ``make test`` runs the basic test suite with ``pytest``
|
||||||
|
- ``make cov`` runs the basic test suite with ``coverage``
|
||||||
|
- ``make test-all`` runs the full test suite with ``tox``
|
||||||
|
- ``make docs`` builds the HTML documentation
|
||||||
|
|
||||||
|
Caution: zero-padded file modes
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
This repository contains several zero-padded file modes that may cause issues
|
||||||
|
when pushing this repository to git hosts other than GitHub. Fixing this is
|
||||||
|
destructive to the commit history, so we suggest ignoring these warnings. If it
|
||||||
|
fails to push and you're using a self-hosted git service like GitLab, you can
|
||||||
|
turn off repository checks in the admin panel.
|
||||||
|
|
||||||
|
These files can also cause issues while cloning. If you have ::
|
||||||
|
|
||||||
|
[fetch]
|
||||||
|
fsckobjects = true
|
||||||
|
|
||||||
|
or ::
|
||||||
|
|
||||||
|
[receive]
|
||||||
|
fsckObjects = true
|
||||||
|
|
||||||
|
set in your git configuration file, cloning this repository will fail. The only
|
||||||
|
solution is to set both of the above settings to false while cloning, and then
|
||||||
|
setting them back to true after the cloning is finished.
|
||||||
|
|
33
Makefile
33
Makefile
|
@ -1,23 +1,35 @@
|
||||||
.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 -q
|
pip install -q -e .[dev]
|
||||||
FLASK_DEBUG= py.test tests examples
|
|
||||||
|
|
||||||
tox-test:
|
test: clean-pyc install-dev
|
||||||
|
pytest
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
tox: test-all
|
||||||
|
|
||||||
|
docs: clean-pyc install-dev
|
||||||
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
audit:
|
audit:
|
||||||
python setup.py audit
|
python setup.py 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 {} +
|
||||||
|
@ -39,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
|
|
||||||
|
|
4
README
4
README
|
@ -33,9 +33,9 @@
|
||||||
|
|
||||||
Good that you're asking. The tests are in the
|
Good that you're asking. The tests are in the
|
||||||
tests/ folder. To run the tests use the
|
tests/ folder. To run the tests use the
|
||||||
`py.test` testing tool:
|
`pytest` testing tool:
|
||||||
|
|
||||||
$ py.test
|
$ pytest
|
||||||
|
|
||||||
Details on contributing can be found in CONTRIBUTING.rst
|
Details on contributing can be found in CONTRIBUTING.rst
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 159 KiB |
|
@ -1,6 +1,6 @@
|
||||||
<h3>About Flask</h3>
|
<h3>About Flask</h3>
|
||||||
<p>
|
<p>
|
||||||
Flask is a micro webdevelopment framework for Python. You are currently
|
Flask is a micro web development framework for Python. You are currently
|
||||||
looking at the documentation of the development version.
|
looking at the documentation of the development version.
|
||||||
</p>
|
</p>
|
||||||
<h3>Other Formats</h3>
|
<h3>Other Formats</h3>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
<h3>Useful Links</h3>
|
<h3>Useful Links</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="http://flask.pocoo.org/">The Flask Website</a></li>
|
<li><a href="http://flask.pocoo.org/">The Flask Website</a></li>
|
||||||
<li><a href="http://pypi.python.org/pypi/Flask">Flask @ PyPI</a></li>
|
<li><a href="https://pypi.python.org/pypi/Flask">Flask @ PyPI</a></li>
|
||||||
<li><a href="http://github.com/pallets/flask">Flask @ GitHub</a></li>
|
<li><a href="https://github.com/pallets/flask">Flask @ GitHub</a></li>
|
||||||
<li><a href="http://github.com/pallets/flask/issues">Issue Tracker</a></li>
|
<li><a href="https://github.com/pallets/flask/issues">Issue Tracker</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9
|
|
84
docs/api.rst
84
docs/api.rst
|
@ -29,62 +29,13 @@ Incoming Request Data
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
.. autoclass:: Request
|
.. autoclass:: Request
|
||||||
:members: is_json, get_json
|
:members:
|
||||||
|
:inherited-members:
|
||||||
.. attribute:: form
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.MultiDict` with the parsed form data from ``POST``
|
|
||||||
or ``PUT`` requests. Please keep in mind that file uploads will not
|
|
||||||
end up here, but instead in the :attr:`files` attribute.
|
|
||||||
|
|
||||||
.. attribute:: args
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.MultiDict` with the parsed contents of the query
|
|
||||||
string. (The part in the URL after the question mark).
|
|
||||||
|
|
||||||
.. attribute:: values
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.CombinedMultiDict` with the contents of both
|
|
||||||
:attr:`form` and :attr:`args`.
|
|
||||||
|
|
||||||
.. attribute:: cookies
|
|
||||||
|
|
||||||
A :class:`dict` with the contents of all cookies transmitted with
|
|
||||||
the request.
|
|
||||||
|
|
||||||
.. attribute:: stream
|
|
||||||
|
|
||||||
If the incoming form data was not encoded with a known mimetype
|
|
||||||
the data is stored unmodified in this stream for consumption. Most
|
|
||||||
of the time it is a better idea to use :attr:`data` which will give
|
|
||||||
you that data as a string. The stream only returns the data once.
|
|
||||||
|
|
||||||
.. attribute:: headers
|
|
||||||
|
|
||||||
The incoming request headers as a dictionary like object.
|
|
||||||
|
|
||||||
.. attribute:: data
|
|
||||||
|
|
||||||
Contains the incoming request data as string in case it came with
|
|
||||||
a mimetype Flask does not handle.
|
|
||||||
|
|
||||||
.. attribute:: files
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.MultiDict` with files uploaded as part of a
|
|
||||||
``POST`` or ``PUT`` request. Each file is stored as
|
|
||||||
:class:`~werkzeug.datastructures.FileStorage` object. It basically behaves like a
|
|
||||||
standard file object you know from Python, with the difference that
|
|
||||||
it also has a :meth:`~werkzeug.datastructures.FileStorage.save` function that can
|
|
||||||
store the file on the filesystem.
|
|
||||||
|
|
||||||
.. attribute:: environ
|
.. attribute:: environ
|
||||||
|
|
||||||
The underlying WSGI environment.
|
The underlying WSGI environment.
|
||||||
|
|
||||||
.. attribute:: method
|
|
||||||
|
|
||||||
The current request method (``POST``, ``GET`` etc.)
|
|
||||||
|
|
||||||
.. attribute:: path
|
.. attribute:: path
|
||||||
.. attribute:: full_path
|
.. attribute:: full_path
|
||||||
.. attribute:: script_root
|
.. attribute:: script_root
|
||||||
|
@ -114,15 +65,8 @@ Incoming Request Data
|
||||||
`url_root` ``u'http://www.example.com/myapplication/'``
|
`url_root` ``u'http://www.example.com/myapplication/'``
|
||||||
============= ======================================================
|
============= ======================================================
|
||||||
|
|
||||||
.. attribute:: is_xhr
|
|
||||||
|
|
||||||
``True`` if the request was triggered via a JavaScript
|
.. attribute:: request
|
||||||
`XMLHttpRequest`. This only works with libraries that support the
|
|
||||||
``X-Requested-With`` header and set it to `XMLHttpRequest`.
|
|
||||||
Libraries that do that are prototype, jQuery and Mochikit and
|
|
||||||
probably some more.
|
|
||||||
|
|
||||||
.. class:: request
|
|
||||||
|
|
||||||
To access incoming request data, you can use the global `request`
|
To access incoming request data, you can use the global `request`
|
||||||
object. Flask parses incoming request data for you and gives you
|
object. Flask parses incoming request data for you and gives you
|
||||||
|
@ -227,18 +171,6 @@ implementation that Flask is using.
|
||||||
.. autoclass:: SessionMixin
|
.. autoclass:: SessionMixin
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. autodata:: session_json_serializer
|
|
||||||
|
|
||||||
This object provides dumping and loading methods similar to simplejson
|
|
||||||
but it also tags certain builtin Python objects that commonly appear in
|
|
||||||
sessions. Currently the following extended values are supported in
|
|
||||||
the JSON it dumps:
|
|
||||||
|
|
||||||
- :class:`~markupsafe.Markup` objects
|
|
||||||
- :class:`~uuid.UUID` objects
|
|
||||||
- :class:`~datetime.datetime` objects
|
|
||||||
- :class:`tuple`\s
|
|
||||||
|
|
||||||
.. admonition:: Notice
|
.. admonition:: Notice
|
||||||
|
|
||||||
The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer
|
The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer
|
||||||
|
@ -316,13 +248,7 @@ Useful Functions and Classes
|
||||||
|
|
||||||
.. autofunction:: url_for
|
.. autofunction:: url_for
|
||||||
|
|
||||||
.. function:: abort(code)
|
.. autofunction:: abort
|
||||||
|
|
||||||
Raises an :exc:`~werkzeug.exceptions.HTTPException` for the given
|
|
||||||
status code. For example to abort request handling with a page not
|
|
||||||
found exception, you would call ``abort(404)``.
|
|
||||||
|
|
||||||
:param code: the HTTP error code.
|
|
||||||
|
|
||||||
.. autofunction:: redirect
|
.. autofunction:: redirect
|
||||||
|
|
||||||
|
@ -416,6 +342,8 @@ you are using Flask 0.10 which implies that:
|
||||||
.. autoclass:: JSONDecoder
|
.. autoclass:: JSONDecoder
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. automodule:: flask.json.tag
|
||||||
|
|
||||||
Template Rendering
|
Template Rendering
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
- the programmer can modify the application object safely.
|
||||||
|
- no request handling happened so far
|
||||||
|
- you have to have a reference to the application object in order to
|
||||||
modify it, there is no magic proxy that can give you a reference to
|
modify it, there is no magic proxy that can give you a reference to
|
||||||
the application object you're currently creating or modifying.
|
the application object you're currently creating or modifying.
|
||||||
|
|
||||||
In contrast, during request handling, a couple of other rules exist:
|
2. In contrast, in the request handling state, a couple of other rules
|
||||||
|
exist:
|
||||||
|
|
||||||
- while a request is active, the context local objects
|
- while a request is active, the context local objects
|
||||||
(:data:`flask.request` and others) point to the current request.
|
(:data:`flask.request` and others) point to the current request.
|
||||||
- any code can get hold of these objects at any time.
|
- any code can get hold of these objects at any time.
|
||||||
|
|
||||||
There is a third state which is sitting in between a little bit.
|
3. There is also a third state somewhere in between 'module-level' and
|
||||||
Sometimes you are dealing with an application in a way that is similar to
|
'request-handling':
|
||||||
how you interact with applications during request handling; just that there
|
|
||||||
is no request active. Consider, for instance, that you're sitting in an
|
Sometimes you are dealing with an application in a way that is similar to
|
||||||
interactive Python shell and interacting with the application, or a
|
how you interact with applications during request handling, but without
|
||||||
command line application.
|
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.
|
||||||
|
|
|
@ -12,7 +12,7 @@ Flask started in part to demonstrate how to build your own framework on top of
|
||||||
existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it
|
existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it
|
||||||
developed, it became useful to a wide audience. As you grow your codebase,
|
developed, it became useful to a wide audience. As you grow your codebase,
|
||||||
don't just use Flask -- understand it. Read the source. Flask's code is
|
don't just use Flask -- understand it. Read the source. Flask's code is
|
||||||
written to be read; it's documentation is published so you can use its internal
|
written to be read; its documentation is published so you can use its internal
|
||||||
APIs. Flask sticks to documented APIs in upstream libraries, and documents its
|
APIs. Flask sticks to documented APIs in upstream libraries, and documents its
|
||||||
internal utilities so that you can find the hook points needed for your
|
internal utilities so that you can find the hook points needed for your
|
||||||
project.
|
project.
|
||||||
|
|
|
@ -245,4 +245,22 @@ Here is an example for a "404 Page Not Found" exception::
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
return render_template('pages/404.html')
|
return render_template('pages/404.html')
|
||||||
|
|
||||||
|
Most errorhandlers will simply work as expected; however, there is a caveat
|
||||||
|
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||||
|
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||||
|
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||||
|
access. This is because the blueprint does not "own" a certain URL space, so
|
||||||
|
the application instance has no way of knowing which blueprint errorhandler it
|
||||||
|
should run if given an invalid URL. If you would like to execute different
|
||||||
|
handling strategies for these errors based on URL prefixes, they may be defined
|
||||||
|
at the application level using the ``request`` proxy object::
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def _handle_api_error(ex):
|
||||||
|
if request.path.startswith('/api/'):
|
||||||
|
return jsonify_error(ex)
|
||||||
|
else:
|
||||||
|
return ex
|
||||||
|
|
||||||
More information on error handling see :ref:`errorpages`.
|
More information on error handling see :ref:`errorpages`.
|
||||||
|
|
63
docs/cli.rst
63
docs/cli.rst
|
@ -56,14 +56,24 @@ If you are constantly working with a virtualenv you can also put the
|
||||||
bottom of the file. That way every time you activate your virtualenv you
|
bottom of the file. That way every time you activate your virtualenv you
|
||||||
automatically also activate the correct application name.
|
automatically also activate the correct application name.
|
||||||
|
|
||||||
|
Edit the activate script for the shell you use. For example:
|
||||||
|
|
||||||
|
Unix Bash: ``venv/bin/activate``::
|
||||||
|
|
||||||
|
FLASK_APP=hello
|
||||||
|
export FLASK_APP
|
||||||
|
|
||||||
|
Windows CMD.exe: ``venv\Scripts\activate.bat``::
|
||||||
|
|
||||||
|
set "FLASK_APP=hello"
|
||||||
|
:END
|
||||||
|
|
||||||
Debug Flag
|
Debug Flag
|
||||||
----------
|
----------
|
||||||
|
|
||||||
The :command:`flask` script can also be instructed to enable the debug
|
The :command:`flask` script can also be instructed to enable the debug
|
||||||
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
||||||
set to ``1`` debug is enabled or ``0`` disables it.
|
set to ``1`` debug is enabled or ``0`` disables it::
|
||||||
|
|
||||||
Or with a filename::
|
|
||||||
|
|
||||||
export FLASK_DEBUG=1
|
export FLASK_DEBUG=1
|
||||||
|
|
||||||
|
@ -141,8 +151,8 @@ This could be a file named :file:`autoapp.py` with these contents::
|
||||||
from yourapplication import create_app
|
from yourapplication import create_app
|
||||||
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
||||||
|
|
||||||
Once this has happened you can make the flask command automatically pick
|
Once this has happened you can make the :command:`flask` command automatically
|
||||||
it up::
|
pick it up::
|
||||||
|
|
||||||
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
||||||
export FLASK_APP=/path/to/autoapp.py
|
export FLASK_APP=/path/to/autoapp.py
|
||||||
|
@ -248,3 +258,46 @@ Inside :file:`mypackage/commands.py` you can then export a Click object::
|
||||||
Once that package is installed in the same virtualenv as Flask itself you
|
Once that package is installed in the same virtualenv as Flask itself you
|
||||||
can run ``flask my-command`` to invoke your command. This is useful to
|
can run ``flask my-command`` to invoke your command. This is useful to
|
||||||
provide extra functionality that Flask itself cannot ship.
|
provide extra functionality that Flask itself cannot ship.
|
||||||
|
|
||||||
|
PyCharm Integration
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The new Flask CLI features aren’t yet fully integrated into the PyCharm IDE,
|
||||||
|
so we have to do a few tweaks to get them working smoothly.
|
||||||
|
|
||||||
|
In your PyCharm application, with your project open, click on *Run*
|
||||||
|
from the menu bar and go to *Edit Configurations*. You’ll be greeted by a
|
||||||
|
screen similar to this:
|
||||||
|
|
||||||
|
.. image:: _static/pycharm-runconfig.png
|
||||||
|
:align: center
|
||||||
|
:class: screenshot
|
||||||
|
:alt: screenshot of pycharm's run configuration settings
|
||||||
|
|
||||||
|
There’s quite a few options to change, but don’t worry— once we’ve done it
|
||||||
|
for one command, we can easily copy the entire configuration and make a
|
||||||
|
single tweak to give us access to other flask cli commands, including
|
||||||
|
any custom ones you may implement yourself.
|
||||||
|
|
||||||
|
For the *Script* input (**A**), we want to navigate to the virtual environment
|
||||||
|
we’re using for our project and within that folder we want to pick the ``flask``
|
||||||
|
file which will reside in the ``bin`` folder, or in the ``Scripts`` folder if
|
||||||
|
you're on Windows.
|
||||||
|
|
||||||
|
The *Script Parameter* field (**B**) is set to the cli command you wish to
|
||||||
|
execute, in this example we use ``run`` which will run our development server.
|
||||||
|
|
||||||
|
We need to add an environment variable (**C**) to identify our application.
|
||||||
|
Click on the browse button and add an entry with ``FLASK_APP`` on the
|
||||||
|
left and the name of the python file, or package on the right
|
||||||
|
(``app.py`` for example).
|
||||||
|
|
||||||
|
Next we need to set the working directory (**D**) to be the same folder where
|
||||||
|
our application file or package resides.
|
||||||
|
|
||||||
|
Finally, untick the *PYTHONPATH* options (**E**) and give the configuration a
|
||||||
|
good descriptive name, such as “Run Flask Server” and click *Apply*.
|
||||||
|
|
||||||
|
Now that we have on run configuration which implements ``flask run`` from within
|
||||||
|
PyCharm, we can simply copy that configuration and alter the script argument
|
||||||
|
to run a different cli command, e.g. ``flask shell``.
|
||||||
|
|
38
docs/conf.py
38
docs/conf.py
|
@ -11,15 +11,17 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from datetime import datetime
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
|
||||||
|
|
||||||
# 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 -----------------------------------------------------
|
||||||
|
@ -35,6 +37,14 @@ extensions = [
|
||||||
'flaskdocext'
|
'flaskdocext'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
__import__('sphinxcontrib.log_cabinet')
|
||||||
|
except ImportError:
|
||||||
|
print('sphinxcontrib-log-cabinet is not installed.')
|
||||||
|
print('Changelog directives will not be re-organized.')
|
||||||
|
else:
|
||||||
|
extensions.append('sphinxcontrib.log_cabinet')
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
@ -49,7 +59,7 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Flask'
|
project = u'Flask'
|
||||||
copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().year)
|
copyright = u'2010 - {0}, Armin Ronacher'.format(BUILD_DATE.year)
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
@ -110,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".
|
||||||
|
@ -231,7 +241,7 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
|
||||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||||
#epub_scheme = ''
|
#epub_scheme = ''
|
||||||
|
|
||||||
# The unique identifier of the text. This can be a ISBN number
|
# The unique identifier of the text. This can be an ISBN number
|
||||||
# or the project homepage.
|
# or the project homepage.
|
||||||
#epub_identifier = ''
|
#epub_identifier = ''
|
||||||
|
|
||||||
|
@ -257,26 +267,14 @@ intersphinx_mapping = {
|
||||||
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
||||||
'click': ('http://click.pocoo.org/', None),
|
'click': ('http://click.pocoo.org/', None),
|
||||||
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
||||||
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
|
'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None),
|
||||||
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
||||||
'blinker': ('https://pythonhosted.org/blinker/', None)
|
'blinker': ('https://pythonhosted.org/blinker/', None)
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
html_theme_options = {
|
||||||
__import__('flask_theme_support')
|
|
||||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
|
||||||
html_theme = 'flask'
|
|
||||||
html_theme_options = {
|
|
||||||
'touch_icon': 'touch-icon.png'
|
'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():
|
||||||
|
|
414
docs/config.rst
414
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
|
||||||
|
@ -44,178 +42,263 @@ method::
|
||||||
SECRET_KEY='...'
|
SECRET_KEY='...'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.. admonition:: Debug Mode with the ``flask`` Script
|
||||||
|
|
||||||
|
If you use the :command:`flask` script to start a local development
|
||||||
|
server, to enable the debug mode, you need to export the ``FLASK_DEBUG``
|
||||||
|
environment variable before running the server::
|
||||||
|
|
||||||
|
$ export FLASK_DEBUG=1
|
||||||
|
$ flask run
|
||||||
|
|
||||||
|
(On Windows you need to use ``set`` instead of ``export``).
|
||||||
|
|
||||||
|
``app.debug`` and ``app.config['DEBUG']`` are not compatible with
|
||||||
|
the :command:`flask` script. They only worked when using ``Flask.run()``
|
||||||
|
method.
|
||||||
|
|
||||||
Builtin Configuration Values
|
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
|
``app.run``, an interactive debugger will be shown for unhanlded
|
||||||
``TESTING`` enable/disable testing mode
|
exceptions, and the server will be reloaded when code changes.
|
||||||
``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`` If the application does not occupy
|
|
||||||
a whole domain or subdomain this can
|
|
||||||
be set to the path where the application
|
|
||||||
is configured to live. This is for
|
|
||||||
session cookie as path value. If
|
|
||||||
domains are used, this should be
|
|
||||||
``None``.
|
|
||||||
``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 cachability.
|
|
||||||
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default)
|
|
||||||
jsonify responses will be pretty printed
|
|
||||||
if they are not requested by an
|
|
||||||
XMLHttpRequest object (controlled by
|
|
||||||
the ``X-Requested-With`` header)
|
|
||||||
``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``
|
||||||
|
@ -262,7 +345,7 @@ So a common pattern is this::
|
||||||
|
|
||||||
This first loads the configuration from the
|
This first loads the configuration from the
|
||||||
`yourapplication.default_settings` module and then overrides the values
|
`yourapplication.default_settings` module and then overrides the values
|
||||||
with the contents of the file the :envvar:``YOURAPPLICATION_SETTINGS``
|
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||||
environment variable points to. This environment variable can be set on
|
environment variable points to. This environment variable can be set on
|
||||||
Linux or OS X with the export command in the shell before starting the
|
Linux or OS X with the export command in the shell before starting the
|
||||||
server::
|
server::
|
||||||
|
@ -463,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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
.. include:: ../CONTRIBUTING.rst
|
|
@ -144,7 +144,7 @@ A basic FastCGI configuration for lighttpd looks like that::
|
||||||
)
|
)
|
||||||
|
|
||||||
alias.url = (
|
alias.url = (
|
||||||
"/static/" => "/path/to/your/static"
|
"/static/" => "/path/to/your/static/"
|
||||||
)
|
)
|
||||||
|
|
||||||
url.rewrite-once = (
|
url.rewrite-once = (
|
||||||
|
@ -159,7 +159,7 @@ work in the URL root you have to work around a lighttpd bug with the
|
||||||
|
|
||||||
Make sure to apply it only if you are mounting the application the URL
|
Make sure to apply it only if you are mounting the application the URL
|
||||||
root. Also, see the Lighty docs for more information on `FastCGI and Python
|
root. Also, see the Lighty docs for more information on `FastCGI and Python
|
||||||
<http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>`_ (note that
|
<https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>`_ (note that
|
||||||
explicitly passing a socket to run() is no longer necessary).
|
explicitly passing a socket to run() is no longer necessary).
|
||||||
|
|
||||||
Configuring nginx
|
Configuring nginx
|
||||||
|
@ -234,7 +234,7 @@ python path. Common problems are:
|
||||||
web server.
|
web server.
|
||||||
- Different python interpreters being used.
|
- Different python interpreters being used.
|
||||||
|
|
||||||
.. _nginx: http://nginx.org/
|
.. _nginx: https://nginx.org/
|
||||||
.. _lighttpd: http://www.lighttpd.net/
|
.. _lighttpd: https://www.lighttpd.net/
|
||||||
.. _cherokee: http://cherokee-project.com/
|
.. _cherokee: http://cherokee-project.com/
|
||||||
.. _flup: https://pypi.python.org/pypi/flup
|
.. _flup: https://pypi.python.org/pypi/flup
|
||||||
|
|
|
@ -21,8 +21,10 @@ Hosted options
|
||||||
- `Deploying Flask on OpenShift <https://developers.openshift.com/en/python-flask.html>`_
|
- `Deploying Flask on OpenShift <https://developers.openshift.com/en/python-flask.html>`_
|
||||||
- `Deploying Flask on Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
- `Deploying Flask on Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
||||||
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
||||||
|
- `Deploying Flask on AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||||
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
||||||
- `Deploying on Azure (IIS) <https://azure.microsoft.com/documentation/articles/web-sites-python-configure/>`_
|
- `Deploying on Azure (IIS) <https://azure.microsoft.com/documentation/articles/web-sites-python-configure/>`_
|
||||||
|
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||||
|
|
||||||
Self-hosted options
|
Self-hosted options
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -30,8 +32,8 @@ Self-hosted options
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
mod_wsgi
|
|
||||||
wsgi-standalone
|
wsgi-standalone
|
||||||
uwsgi
|
uwsgi
|
||||||
|
mod_wsgi
|
||||||
fastcgi
|
fastcgi
|
||||||
cgi
|
cgi
|
||||||
|
|
|
@ -13,7 +13,7 @@ If you are using the `Apache`_ webserver, consider using `mod_wsgi`_.
|
||||||
not called because this will always start a local WSGI server which
|
not called because this will always start a local WSGI server which
|
||||||
we do not want if we deploy that application to mod_wsgi.
|
we do not want if we deploy that application to mod_wsgi.
|
||||||
|
|
||||||
.. _Apache: http://httpd.apache.org/
|
.. _Apache: https://httpd.apache.org/
|
||||||
|
|
||||||
Installing `mod_wsgi`
|
Installing `mod_wsgi`
|
||||||
---------------------
|
---------------------
|
||||||
|
@ -114,7 +114,7 @@ refuse to run with the above configuration. On a Windows system, eliminate those
|
||||||
|
|
||||||
Note: There have been some changes in access control configuration for `Apache 2.4`_.
|
Note: There have been some changes in access control configuration for `Apache 2.4`_.
|
||||||
|
|
||||||
.. _Apache 2.4: http://httpd.apache.org/docs/trunk/upgrading.html
|
.. _Apache 2.4: https://httpd.apache.org/docs/trunk/upgrading.html
|
||||||
|
|
||||||
Most notably, the syntax for directory permissions has changed from httpd 2.2
|
Most notably, the syntax for directory permissions has changed from httpd 2.2
|
||||||
|
|
||||||
|
@ -130,12 +130,12 @@ to httpd 2.4 syntax
|
||||||
Require all granted
|
Require all granted
|
||||||
|
|
||||||
|
|
||||||
For more information consult the `mod_wsgi wiki`_.
|
For more information consult the `mod_wsgi documentation`_.
|
||||||
|
|
||||||
.. _mod_wsgi: http://code.google.com/p/modwsgi/
|
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
||||||
.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide
|
.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html
|
||||||
.. _virtual python: https://pypi.python.org/pypi/virtualenv
|
.. _virtual python: https://pypi.python.org/pypi/virtualenv
|
||||||
.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/w/list
|
.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -66,7 +66,7 @@ to have it in the URL root its a bit simpler::
|
||||||
uwsgi_pass unix:/tmp/yourapplication.sock;
|
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
.. _nginx: http://nginx.org/
|
.. _nginx: https://nginx.org/
|
||||||
.. _lighttpd: http://www.lighttpd.net/
|
.. _lighttpd: https://www.lighttpd.net/
|
||||||
.. _cherokee: http://cherokee-project.com/
|
.. _cherokee: http://cherokee-project.com/
|
||||||
.. _uwsgi: http://projects.unbit.it/uwsgi/
|
.. _uwsgi: http://projects.unbit.it/uwsgi/
|
||||||
|
|
|
@ -27,6 +27,22 @@ For example, to run a Flask application with 4 worker processes (``-w
|
||||||
.. _eventlet: http://eventlet.net/
|
.. _eventlet: http://eventlet.net/
|
||||||
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
|
||||||
|
|
||||||
|
uWSGI
|
||||||
|
--------
|
||||||
|
|
||||||
|
`uWSGI`_ is a fast application server written in C. It is very configurable
|
||||||
|
which makes it more complicated to setup than gunicorn.
|
||||||
|
|
||||||
|
Running `uWSGI HTTP Router`_::
|
||||||
|
|
||||||
|
uwsgi --http 127.0.0.1:5000 --module myproject:app
|
||||||
|
|
||||||
|
For a more optimized setup, see `configuring uWSGI and NGINX`_.
|
||||||
|
|
||||||
|
.. _uWSGI: http://uwsgi-docs.readthedocs.io/en/latest/
|
||||||
|
.. _uWSGI HTTP Router: http://uwsgi-docs.readthedocs.io/en/latest/HTTP.html#the-uwsgi-http-https-router
|
||||||
|
.. _configuring uWSGI and NGINX: uwsgi.html#starting-your-app-with-uwsgi
|
||||||
|
|
||||||
Gevent
|
Gevent
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ Error Logging Tools
|
||||||
Sending error mails, even if just for critical ones, can become
|
Sending error mails, even if just for critical ones, can become
|
||||||
overwhelming if enough users are hitting the error and log files are
|
overwhelming if enough users are hitting the error and log files are
|
||||||
typically never looked at. This is why we recommend using `Sentry
|
typically never looked at. This is why we recommend using `Sentry
|
||||||
<http://www.getsentry.com/>`_ for dealing with application errors. It's
|
<https://www.getsentry.com/>`_ for dealing with application errors. It's
|
||||||
available as an Open Source project `on GitHub
|
available as an Open Source project `on GitHub
|
||||||
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
||||||
<https://getsentry.com/signup/>`_ which you can try for free. Sentry
|
<https://getsentry.com/signup/>`_ which you can try for free. Sentry
|
||||||
|
@ -51,7 +51,7 @@ And then add this to your Flask app::
|
||||||
from raven.contrib.flask import Sentry
|
from raven.contrib.flask import Sentry
|
||||||
sentry = Sentry(app, dsn='YOUR_DSN_HERE')
|
sentry = Sentry(app, dsn='YOUR_DSN_HERE')
|
||||||
|
|
||||||
Of if you are using factories you can also init it later::
|
Or if you are using factories you can also init it later::
|
||||||
|
|
||||||
from raven.contrib.flask import Sentry
|
from raven.contrib.flask import Sentry
|
||||||
sentry = Sentry(dsn='YOUR_DSN_HERE')
|
sentry = Sentry(dsn='YOUR_DSN_HERE')
|
||||||
|
@ -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 rised 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
|
||||||
-----------
|
-----------
|
||||||
|
@ -216,7 +239,7 @@ A formatter can be instantiated with a format string. Note that
|
||||||
tracebacks are appended to the log entry automatically. You don't have to
|
tracebacks are appended to the log entry automatically. You don't have to
|
||||||
do that in the log formatter format string.
|
do that in the log formatter format string.
|
||||||
|
|
||||||
Here some example setups:
|
Here are some example setups:
|
||||||
|
|
||||||
Email
|
Email
|
||||||
`````
|
`````
|
||||||
|
@ -276,8 +299,9 @@ that this list is not complete, consult the official documentation of the
|
||||||
| ``%(lineno)d`` | Source line number where the logging call was |
|
| ``%(lineno)d`` | Source line number where the logging call was |
|
||||||
| | issued (if available). |
|
| | issued (if available). |
|
||||||
+------------------+----------------------------------------------------+
|
+------------------+----------------------------------------------------+
|
||||||
| ``%(asctime)s`` | Human-readable time when the LogRecord` was |
|
| ``%(asctime)s`` | Human-readable time when the |
|
||||||
| | created. By default this is of the form |
|
| | :class:`~logging.LogRecord` was created. |
|
||||||
|
| | By default this is of the form |
|
||||||
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
||||||
| | the comma are millisecond portion of the time). |
|
| | the comma are millisecond portion of the time). |
|
||||||
| | This can be changed by subclassing the formatter |
|
| | This can be changed by subclassing the formatter |
|
||||||
|
|
|
@ -29,12 +29,6 @@ be something like "Flask-SimpleXML". Make sure to include the name
|
||||||
This is how users can then register dependencies to your extension in
|
This is how users can then register dependencies to your extension in
|
||||||
their :file:`setup.py` files.
|
their :file:`setup.py` files.
|
||||||
|
|
||||||
Flask sets up a redirect package called :data:`flask.ext` where users
|
|
||||||
should import the extensions from. If you for instance have a package
|
|
||||||
called ``flask_something`` users would import it as
|
|
||||||
``flask.ext.something``. This is done to transition from the old
|
|
||||||
namespace packages. See :ref:`ext-import-transition` for more details.
|
|
||||||
|
|
||||||
But what do extensions look like themselves? An extension has to ensure
|
But what do extensions look like themselves? An extension has to ensure
|
||||||
that it works with multiple Flask application instances at once. This is
|
that it works with multiple Flask application instances at once. This is
|
||||||
a requirement because many people will use patterns like the
|
a requirement because many people will use patterns like the
|
||||||
|
@ -48,7 +42,7 @@ that people can easily install the development version into their
|
||||||
virtualenv without having to download the library by hand.
|
virtualenv without having to download the library by hand.
|
||||||
|
|
||||||
Flask extensions must be licensed under a BSD, MIT or more liberal license
|
Flask extensions must be licensed under a BSD, MIT or more liberal license
|
||||||
to be able to be enlisted in the Flask Extension Registry. Keep in mind
|
in order to be listed in the Flask Extension Registry. Keep in mind
|
||||||
that the Flask Extension Registry is a moderated place and libraries will
|
that the Flask Extension Registry is a moderated place and libraries will
|
||||||
be reviewed upfront if they behave as required.
|
be reviewed upfront if they behave as required.
|
||||||
|
|
||||||
|
@ -154,10 +148,10 @@ What to use depends on what you have in mind. For the SQLite 3 extension
|
||||||
we will use the class-based approach because it will provide users with an
|
we will use the class-based approach because it will provide users with an
|
||||||
object that handles opening and closing database connections.
|
object that handles opening and closing database connections.
|
||||||
|
|
||||||
What's important about classes is that they encourage to be shared around
|
When designing your classes, it's important to make them easily reusable
|
||||||
on module level. In that case, the object itself must not under any
|
at the module level. This means the object itself must not under any
|
||||||
circumstances store any application specific state and must be shareable
|
circumstances store any application specific state and must be shareable
|
||||||
between different application.
|
between different applications.
|
||||||
|
|
||||||
The Extension Code
|
The Extension Code
|
||||||
------------------
|
------------------
|
||||||
|
@ -334,10 +328,10 @@ development. If you want to learn more, it's a very good idea to check
|
||||||
out existing extensions on the `Flask Extension Registry`_. If you feel
|
out existing extensions on the `Flask Extension Registry`_. If you feel
|
||||||
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
|
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
|
||||||
ideas for nice looking APIs. Especially if you do something nobody before
|
ideas for nice looking APIs. Especially if you do something nobody before
|
||||||
you did, it might be a very good idea to get some more input. This not
|
you did, it might be a very good idea to get some more input. This not only
|
||||||
only to get an idea about what people might want to have from an
|
generates useful feedback on what people might want from an extension, but
|
||||||
extension, but also to avoid having multiple developers working on pretty
|
also avoids having multiple developers working in isolation on pretty much the
|
||||||
much the same side by side.
|
same problem.
|
||||||
|
|
||||||
Remember: good API design is hard, so introduce your project on the
|
Remember: good API design is hard, so introduce your project on the
|
||||||
mailinglist, and let other developers give you a helping hand with
|
mailinglist, and let other developers give you a helping hand with
|
||||||
|
@ -389,11 +383,9 @@ extension to be approved you have to follow these guidelines:
|
||||||
(``PackageName==dev``).
|
(``PackageName==dev``).
|
||||||
9. The ``zip_safe`` flag in the setup script must be set to ``False``,
|
9. The ``zip_safe`` flag in the setup script must be set to ``False``,
|
||||||
even if the extension would be safe for zipping.
|
even if the extension would be safe for zipping.
|
||||||
10. An extension currently has to support Python 2.6 as well as
|
10. An extension currently has to support Python 2.7, Python 3.3 and higher.
|
||||||
Python 2.7
|
|
||||||
|
|
||||||
|
|
||||||
.. _ext-import-transition:
|
|
||||||
|
|
||||||
Extension Import Transition
|
Extension Import Transition
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -413,6 +405,6 @@ schema. The ``flask.ext.foo`` compatibility alias is still in Flask 0.11 but is
|
||||||
now deprecated -- you should use ``flask_foo``.
|
now deprecated -- you should use ``flask_foo``.
|
||||||
|
|
||||||
|
|
||||||
.. _OAuth extension: http://pythonhosted.org/Flask-OAuth/
|
.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/
|
||||||
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
||||||
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
||||||
|
|
|
@ -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'
|
|
||||||
}
|
|
|
@ -3,163 +3,173 @@
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
Flask depends on some external libraries, like `Werkzeug
|
Python Version
|
||||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_.
|
--------------
|
||||||
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
|
||||||
applications and a variety of servers for both development and deployment.
|
|
||||||
Jinja2 renders templates.
|
|
||||||
|
|
||||||
So how do you get all that on your computer quickly? There are many ways you
|
We recommend using the latest version of Python 3. Flask supports Python 3.3
|
||||||
could do that, but the most kick-ass method is virtualenv, so let's have a look
|
and newer, Python 2.6 and newer, and PyPy.
|
||||||
at that first.
|
|
||||||
|
|
||||||
You will need Python 2.6 or newer to get started, so be sure to have an
|
Dependencies
|
||||||
up-to-date Python 2.x installation. For using Flask with Python 3 have a
|
------------
|
||||||
look at :ref:`python3-support`.
|
|
||||||
|
|
||||||
.. _virtualenv:
|
These distributions will be installed automatically when installing Flask.
|
||||||
|
|
||||||
virtualenv
|
* `Werkzeug`_ implements WSGI, the standard Python interface between
|
||||||
----------
|
applications and servers.
|
||||||
|
* `Jinja`_ is a template language that renders the pages your application
|
||||||
|
serves.
|
||||||
|
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
|
||||||
|
templates to avoid injection attacks.
|
||||||
|
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
|
||||||
|
to protect Flask's session cookie.
|
||||||
|
* `Click`_ is a framework for writing command line applications. It provides
|
||||||
|
the ``flask`` command and allows adding custom management commands.
|
||||||
|
|
||||||
Virtualenv is probably what you want to use during development, and if you have
|
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||||
shell access to your production machines, you'll probably want to use it there,
|
.. _Jinja: http://jinja.pocoo.org/
|
||||||
too.
|
.. _MarkupSafe: https://pypi.python.org/pypi/MarkupSafe
|
||||||
|
.. _ItsDangerous: https://pythonhosted.org/itsdangerous/
|
||||||
|
.. _Click: http://click.pocoo.org/
|
||||||
|
|
||||||
What problem does virtualenv solve? If you like Python as much as I do,
|
Optional dependencies
|
||||||
chances are you want to use it for other projects besides Flask-based web
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
applications. But the more projects you have, the more likely it is that you
|
|
||||||
will be working with different versions of Python itself, or at least different
|
|
||||||
versions of Python libraries. Let's face it: quite often libraries break
|
|
||||||
backwards compatibility, and it's unlikely that any serious application will
|
|
||||||
have zero dependencies. So what do you do if two or more of your projects have
|
|
||||||
conflicting dependencies?
|
|
||||||
|
|
||||||
Virtualenv to the rescue! Virtualenv enables multiple side-by-side
|
These distributions will not be installed automatically. Flask will detect and
|
||||||
installations of Python, one for each project. It doesn't actually install
|
use them if you install them.
|
||||||
separate copies of Python, but it does provide a clever way to keep different
|
|
||||||
project environments isolated. Let's see how virtualenv works.
|
|
||||||
|
|
||||||
If you are on Mac OS X or Linux, chances are that the following
|
* `Blinker`_ provides support for :ref:`signals`.
|
||||||
command will work for you::
|
* `SimpleJSON`_ is a fast JSON implementation that is compatible with
|
||||||
|
Python's ``json`` module. It is preferred for JSON operations if it is
|
||||||
|
installed.
|
||||||
|
|
||||||
$ sudo pip install virtualenv
|
.. _Blinker: https://pythonhosted.org/blinker/
|
||||||
|
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
||||||
|
|
||||||
It will probably install virtualenv on your system. Maybe it's even
|
Virtual environments
|
||||||
in your package manager. If you use Ubuntu, try::
|
--------------------
|
||||||
|
|
||||||
$ sudo apt-get install python-virtualenv
|
Use a virtual environment to manage the dependencies for your project, both in
|
||||||
|
development and in production.
|
||||||
|
|
||||||
If you are on Windows and don't have the ``easy_install`` command, you must
|
What problem does a virtual environment solve? The more Python projects you
|
||||||
install it first. Check the :ref:`windows-easy-install` section for more
|
have, the more likely it is that you need to work with different versions of
|
||||||
information about how to do that. Once you have it installed, run the same
|
Python libraries, or even Python itself. Newer versions of libraries for one
|
||||||
commands as above, but without the ``sudo`` prefix.
|
project can break compatibility in another project.
|
||||||
|
|
||||||
Once you have virtualenv installed, just fire up a shell and create
|
Virtual environments are independent groups of Python libraries, one for each
|
||||||
your own environment. I usually create a project folder and a :file:`venv`
|
project. Packages installed for one project will not affect other projects or
|
||||||
folder within::
|
the operating system's packages.
|
||||||
|
|
||||||
$ mkdir myproject
|
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
||||||
$ cd myproject
|
environments. If you're using a modern version of Python, you can continue on
|
||||||
$ virtualenv venv
|
to the next section.
|
||||||
New python executable in venv/bin/python
|
|
||||||
Installing setuptools, pip............done.
|
|
||||||
|
|
||||||
Now, whenever you want to work on a project, you only have to activate the
|
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
||||||
corresponding environment. On OS X and Linux, do the following::
|
|
||||||
|
|
||||||
$ . venv/bin/activate
|
.. _install-create-env:
|
||||||
|
|
||||||
If you are a Windows user, the following command is for you::
|
Create an environment
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
$ venv\scripts\activate
|
Create a project folder and a :file:`venv` folder within:
|
||||||
|
|
||||||
Either way, you should now be using your virtualenv (notice how the prompt of
|
.. code-block:: sh
|
||||||
your shell has changed to show the active environment).
|
|
||||||
|
|
||||||
And if you want to go back to the real world, use the following command::
|
mkdir myproject
|
||||||
|
cd myproject
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
$ deactivate
|
On Windows:
|
||||||
|
|
||||||
After doing this, the prompt of your shell should be as familiar as before.
|
.. code-block:: bat
|
||||||
|
|
||||||
Now, let's move on. Enter the following command to get Flask activated in your
|
py -3 -m venv venv
|
||||||
virtualenv::
|
|
||||||
|
|
||||||
$ pip install Flask
|
If you needed to install virtualenv because you are on an older version of
|
||||||
|
Python, use the following command instead:
|
||||||
|
|
||||||
A few seconds later and you are good to go.
|
.. code-block:: sh
|
||||||
|
|
||||||
|
virtualenv venv
|
||||||
|
|
||||||
System-Wide Installation
|
On Windows:
|
||||||
------------------------
|
|
||||||
|
|
||||||
This is possible as well, though I do not recommend it. Just run
|
.. code-block:: bat
|
||||||
``pip`` with root privileges::
|
|
||||||
|
|
||||||
$ sudo pip install Flask
|
\Python27\Scripts\virtualenv.exe venv
|
||||||
|
|
||||||
(On Windows systems, run it in a command-prompt window with administrator
|
Activate the environment
|
||||||
privileges, and leave out ``sudo``.)
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Before you work on your project, activate the corresponding environment:
|
||||||
|
|
||||||
Living on the Edge
|
.. code-block:: sh
|
||||||
|
|
||||||
|
. venv/bin/activate
|
||||||
|
|
||||||
|
On Windows:
|
||||||
|
|
||||||
|
.. code-block:: bat
|
||||||
|
|
||||||
|
venv\Scripts\activate
|
||||||
|
|
||||||
|
Your shell prompt will change to show the name of the activated environment.
|
||||||
|
|
||||||
|
Install Flask
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Within the activated environment, use the following command to install Flask:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
pip install Flask
|
||||||
|
|
||||||
|
Living on the edge
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want to work with the latest Flask code before it's released, install or
|
||||||
|
update the code from the master branch:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
pip install -U https://github.com/pallets/flask/archive/master.tar.gz
|
||||||
|
|
||||||
|
.. _install-install-virtualenv:
|
||||||
|
|
||||||
|
Install virtualenv
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If you want to work with the latest version of Flask, there are two ways: you
|
If you are using Python 2, the venv module is not available. Instead,
|
||||||
can either let ``pip`` pull in the development version, or you can tell
|
install `virtualenv`_.
|
||||||
it to operate on a git checkout. Either way, virtualenv is recommended.
|
|
||||||
|
|
||||||
Get the git checkout in a new virtualenv and run in development mode::
|
On Linux, virtualenv is provided by your package manager:
|
||||||
|
|
||||||
$ git clone http://github.com/pallets/flask.git
|
.. code-block:: sh
|
||||||
Initialized empty Git repository in ~/dev/flask/.git/
|
|
||||||
$ cd flask
|
|
||||||
$ virtualenv venv
|
|
||||||
New python executable in venv/bin/python
|
|
||||||
Installing setuptools, pip............done.
|
|
||||||
$ . venv/bin/activate
|
|
||||||
$ python setup.py develop
|
|
||||||
...
|
|
||||||
Finished processing dependencies for Flask
|
|
||||||
|
|
||||||
This will pull in the dependencies and activate the git head as the current
|
# Debian, Ubuntu
|
||||||
version inside the virtualenv. Then all you have to do is run ``git pull
|
sudo apt-get install python-virtualenv
|
||||||
origin`` to update to the latest version.
|
|
||||||
|
|
||||||
.. _windows-easy-install:
|
# CentOS, Fedora
|
||||||
|
sudo yum install python-virtualenv
|
||||||
|
|
||||||
`pip` and `setuptools` on Windows
|
# Arch
|
||||||
---------------------------------
|
sudo pacman -S python-virtualenv
|
||||||
|
|
||||||
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools``
|
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
|
||||||
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial
|
|
||||||
package you will need is pip - this will let you install
|
|
||||||
anything else (like virtualenv). Fortunately there is a "bootstrap script"
|
|
||||||
you can run to install.
|
|
||||||
|
|
||||||
If you don't currently have ``pip``, then `get-pip.py` will install it for you.
|
.. code-block:: sh
|
||||||
|
|
||||||
`get-pip.py`_
|
sudo python2 Downloads/get-pip.py
|
||||||
|
sudo python2 -m pip install virtualenv
|
||||||
|
|
||||||
It should be double-clickable once you download it. If you already have ``pip``,
|
On Windows, as an administrator:
|
||||||
you can upgrade them by running::
|
|
||||||
|
|
||||||
> pip install --upgrade pip setuptools
|
.. code-block:: bat
|
||||||
|
|
||||||
Most often, once you pull up a command prompt you want to be able to type ``pip``
|
\Python27\python.exe Downloads\get-pip.py
|
||||||
and ``python`` which will run those things, but this might not automatically happen
|
\Python27\python.exe -m pip install virtualenv
|
||||||
on Windows, because it doesn't know where those executables are (give either a try!).
|
|
||||||
|
|
||||||
To fix this, you should be able to navigate to your Python install directory
|
Now you can continue to :ref:`install-create-env`.
|
||||||
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
|
|
||||||
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
|
|
||||||
check that you can now just type ``python`` to bring up the interpreter.
|
|
||||||
|
|
||||||
Finally, to install `virtualenv`_, you can simply run::
|
|
||||||
|
|
||||||
> pip install virtualenv
|
|
||||||
|
|
||||||
Then you can be off on your way following the installation instructions above.
|
|
||||||
|
|
||||||
|
.. _virtualenv: https://virtualenv.pypa.io/
|
||||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||||
|
|
|
@ -6,7 +6,7 @@ Application Factories
|
||||||
If you are already using packages and blueprints for your application
|
If you are already using packages and blueprints for your application
|
||||||
(:ref:`blueprints`) there are a couple of really nice ways to further improve
|
(:ref:`blueprints`) there are a couple of really nice ways to further improve
|
||||||
the experience. A common pattern is creating the application object when
|
the experience. A common pattern is creating the application object when
|
||||||
the blueprint is imported. But if you move the creation of this object,
|
the blueprint is imported. But if you move the creation of this object
|
||||||
into a function, you can then create multiple instances of this app later.
|
into a function, you can then create multiple instances of this app later.
|
||||||
|
|
||||||
So why would you want to do this?
|
So why would you want to do this?
|
||||||
|
@ -60,7 +60,7 @@ Factories & Extensions
|
||||||
It's preferable to create your extensions and app factories so that the
|
It's preferable to create your extensions and app factories so that the
|
||||||
extension object does not initially get bound to the application.
|
extension object does not initially get bound to the application.
|
||||||
|
|
||||||
Using `Flask-SQLAlchemy <http://pythonhosted.org/Flask-SQLAlchemy/>`_,
|
Using `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_,
|
||||||
as an example, you should not do something along those lines::
|
as an example, you should not do something along those lines::
|
||||||
|
|
||||||
def create_app(config_filename):
|
def create_app(config_filename):
|
||||||
|
|
|
@ -1,24 +1,27 @@
|
||||||
Celery Based Background Tasks
|
Celery Background Tasks
|
||||||
=============================
|
=======================
|
||||||
|
|
||||||
Celery is a task queue for Python with batteries included. It used to
|
If your application has a long running task, such as processing some uploaded
|
||||||
have a Flask integration but it became unnecessary after some
|
data or sending email, you don't want to wait for it to finish during a
|
||||||
restructuring of the internals of Celery with Version 3. This guide fills
|
request. Instead, use a task queue to send the necessary data to another
|
||||||
in the blanks in how to properly use Celery with Flask but assumes that
|
process that will run the task in the background while the request returns
|
||||||
you generally already read the `First Steps with Celery
|
immediately.
|
||||||
<http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
|
|
||||||
guide in the official Celery documentation.
|
|
||||||
|
|
||||||
Installing Celery
|
Celery is a powerful task queue that can be used for simple background tasks
|
||||||
-----------------
|
as well as complex multi-stage programs and schedules. This guide will show you
|
||||||
|
how to configure Celery using Flask, but assumes you've already read the
|
||||||
|
`First Steps with Celery <http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
|
||||||
|
guide in the Celery documentation.
|
||||||
|
|
||||||
Celery is on the Python Package Index (PyPI), so it can be installed with
|
Install
|
||||||
standard Python tools like :command:`pip` or :command:`easy_install`::
|
-------
|
||||||
|
|
||||||
|
Celery is a separate Python package. Install it from PyPI using pip::
|
||||||
|
|
||||||
$ pip install celery
|
$ pip install celery
|
||||||
|
|
||||||
Configuring Celery
|
Configure
|
||||||
------------------
|
---------
|
||||||
|
|
||||||
The first thing you need is a Celery instance, this is called the celery
|
The first thing you need is a Celery instance, this is called the celery
|
||||||
application. It serves the same purpose as the :class:`~flask.Flask`
|
application. It serves the same purpose as the :class:`~flask.Flask`
|
||||||
|
@ -36,15 +39,18 @@ This is all that is necessary to properly integrate Celery with Flask::
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
def make_celery(app):
|
def make_celery(app):
|
||||||
celery = Celery(app.import_name, backend=app.config['CELERY_BACKEND'],
|
celery = Celery(
|
||||||
broker=app.config['CELERY_BROKER_URL'])
|
app.import_name,
|
||||||
|
backend=app.config['CELERY_RESULT_BACKEND'],
|
||||||
|
broker=app.config['CELERY_BROKER_URL']
|
||||||
|
)
|
||||||
celery.conf.update(app.config)
|
celery.conf.update(app.config)
|
||||||
TaskBase = celery.Task
|
|
||||||
class ContextTask(TaskBase):
|
class ContextTask(celery.Task):
|
||||||
abstract = True
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
return TaskBase.__call__(self, *args, **kwargs)
|
return self.run(*args, **kwargs)
|
||||||
|
|
||||||
celery.Task = ContextTask
|
celery.Task = ContextTask
|
||||||
return celery
|
return celery
|
||||||
|
|
||||||
|
@ -53,11 +59,12 @@ from the application config, updates the rest of the Celery config from
|
||||||
the Flask config and then creates a subclass of the task that wraps the
|
the Flask config and then creates a subclass of the task that wraps the
|
||||||
task execution in an application context.
|
task execution in an application context.
|
||||||
|
|
||||||
Minimal Example
|
An example task
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
With what we have above this is the minimal example of using Celery with
|
Let's write a task that adds two numbers together and returns the result. We
|
||||||
Flask::
|
configure Celery's broker and backend to use Redis, create a ``celery``
|
||||||
|
application using the factor from above, and then use it to define the task. ::
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
@ -68,26 +75,27 @@ Flask::
|
||||||
)
|
)
|
||||||
celery = make_celery(flask_app)
|
celery = make_celery(flask_app)
|
||||||
|
|
||||||
|
|
||||||
@celery.task()
|
@celery.task()
|
||||||
def add_together(a, b):
|
def add_together(a, b):
|
||||||
return a + b
|
return a + b
|
||||||
|
|
||||||
This task can now be called in the background:
|
This task can now be called in the background::
|
||||||
|
|
||||||
>>> result = add_together.delay(23, 42)
|
result = add_together.delay(23, 42)
|
||||||
>>> result.wait()
|
result.wait() # 65
|
||||||
65
|
|
||||||
|
|
||||||
Running the Celery Worker
|
Run a worker
|
||||||
-------------------------
|
------------
|
||||||
|
|
||||||
Now if you jumped in and already executed the above code you will be
|
If you jumped in and already executed the above code you will be
|
||||||
disappointed to learn that your ``.wait()`` will never actually return.
|
disappointed to learn that ``.wait()`` will never actually return.
|
||||||
That's because you also need to run celery. You can do that by running
|
That's because you also need to run a Celery worker to receive and execute the
|
||||||
celery as a worker::
|
task. ::
|
||||||
|
|
||||||
$ celery -A your_application.celery worker
|
$ celery -A your_application.celery worker
|
||||||
|
|
||||||
The ``your_application`` string has to point to your application's package
|
The ``your_application`` string has to point to your application's package
|
||||||
or module that creates the `celery` object.
|
or module that creates the ``celery`` object.
|
||||||
|
|
||||||
|
Now that the worker is running, ``wait`` will return the result once the task
|
||||||
|
is finished.
|
||||||
|
|
|
@ -3,71 +3,43 @@
|
||||||
Deferred Request Callbacks
|
Deferred Request Callbacks
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
One of the design principles of Flask is that response objects are created
|
One of the design principles of Flask is that response objects are created and
|
||||||
and passed down a chain of potential callbacks that can modify them or
|
passed down a chain of potential callbacks that can modify them or replace
|
||||||
replace them. When the request handling starts, there is no response
|
them. When the request handling starts, there is no response object yet. It is
|
||||||
object yet. It is created as necessary either by a view function or by
|
created as necessary either by a view function or by some other component in
|
||||||
some other component in the system.
|
the system.
|
||||||
|
|
||||||
But what happens if you want to modify the response at a point where the
|
What happens if you want to modify the response at a point where the response
|
||||||
response does not exist yet? A common example for that would be a
|
does not exist yet? A common example for that would be a
|
||||||
before-request function that wants to set a cookie on the response object.
|
:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
|
||||||
|
response object.
|
||||||
|
|
||||||
One way is to avoid the situation. Very often that is possible. For
|
One way is to avoid the situation. Very often that is possible. For instance
|
||||||
instance you can try to move that logic into an after-request callback
|
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
||||||
instead. Sometimes however moving that code there is just not a very
|
callback instead. However, sometimes moving code there makes it more
|
||||||
pleasant experience or makes code look very awkward.
|
more complicated or awkward to reason about.
|
||||||
|
|
||||||
As an alternative possibility you can attach a bunch of callback functions
|
As an alternative, you can use :func:`~flask.after_this_request` to register
|
||||||
to the :data:`~flask.g` object and call them at the end of the request.
|
callbacks that will execute after only the current request. This way you can
|
||||||
This way you can defer code execution from anywhere in the application.
|
defer code execution from anywhere in the application, based on the current
|
||||||
|
request.
|
||||||
|
|
||||||
The Decorator
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The following decorator is the key. It registers a function on a list on
|
|
||||||
the :data:`~flask.g` object::
|
|
||||||
|
|
||||||
from flask import g
|
|
||||||
|
|
||||||
def after_this_request(f):
|
|
||||||
if not hasattr(g, 'after_request_callbacks'):
|
|
||||||
g.after_request_callbacks = []
|
|
||||||
g.after_request_callbacks.append(f)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
Calling the Deferred
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Now you can use the `after_this_request` decorator to mark a function to
|
|
||||||
be called at the end of the request. But we still need to call them. For
|
|
||||||
this the following function needs to be registered as
|
|
||||||
:meth:`~flask.Flask.after_request` callback::
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def call_after_request_callbacks(response):
|
|
||||||
for callback in getattr(g, 'after_request_callbacks', ()):
|
|
||||||
callback(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
A Practical Example
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
At any time during a request, we can register a function to be called at the
|
At any time during a request, we can register a function to be called at the
|
||||||
end of the request. For example you can remember the current language of the
|
end of the request. For example you can remember the current language of the
|
||||||
user in a cookie in the before-request function::
|
user in a cookie in a :meth:`~flask.Flask.before_request` callback::
|
||||||
|
|
||||||
from flask import request
|
from flask import request, after_this_request
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def detect_user_language():
|
def detect_user_language():
|
||||||
language = request.cookies.get('user_lang')
|
language = request.cookies.get('user_lang')
|
||||||
|
|
||||||
if language is None:
|
if language is None:
|
||||||
language = guess_language_from_request()
|
language = guess_language_from_request()
|
||||||
|
|
||||||
|
# when the response exists, set a cookie with the language
|
||||||
@after_this_request
|
@after_this_request
|
||||||
def remember_language(response):
|
def remember_language(response):
|
||||||
response.set_cookie('user_lang', language)
|
response.set_cookie('user_lang', language)
|
||||||
|
|
||||||
g.language = language
|
g.language = language
|
||||||
|
|
|
@ -174,4 +174,4 @@ the code without having to run ``install`` again after each change.
|
||||||
|
|
||||||
|
|
||||||
.. _pip: https://pypi.python.org/pypi/pip
|
.. _pip: https://pypi.python.org/pypi/pip
|
||||||
.. _Setuptools: https://pythonhosted.org/setuptools
|
.. _Setuptools: https://pypi.python.org/pypi/setuptools
|
||||||
|
|
|
@ -47,28 +47,45 @@ even if the application behaves correctly:
|
||||||
Error Handlers
|
Error Handlers
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
An error handler is a function, just like a view function, but it is
|
An error handler is a function that returns a response when a type of error is
|
||||||
called when an error happens and is passed that error. The error is most
|
raised, similar to how a view is a function that returns a response when a
|
||||||
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
request URL is matched. It is passed the instance of the error being handled,
|
||||||
can be a different error: a handler for internal server errors will be
|
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
|
||||||
passed other exception instances as well if they are uncaught.
|
handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
||||||
|
addition to explicit 500 errors.
|
||||||
|
|
||||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||||
decorator and the error code of the exception. Keep in mind that Flask
|
decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
|
||||||
will *not* set the error code for you, so make sure to also provide the
|
can be registered for a status code, like 404, or for an exception class.
|
||||||
HTTP status code when returning a response.
|
|
||||||
|
|
||||||
Please note that if you add an error handler for "500 Internal Server
|
The status code of the response will not be set to the handler's code. Make
|
||||||
Error", Flask will not trigger it if it's running in Debug mode.
|
sure to provide the appropriate HTTP status code when returning a response from
|
||||||
|
a handler.
|
||||||
|
|
||||||
Here an example implementation for a "404 Page Not Found" exception::
|
A handler for "500 Internal Server Error" will not be used when running in
|
||||||
|
debug mode. Instead, the interactive debugger will be shown.
|
||||||
|
|
||||||
|
Here is an example implementation for a "404 Page Not Found" exception::
|
||||||
|
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
|
# note that we set the 404 status explicitly
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
When using the :ref:`application factory pattern <app-factories>`::
|
||||||
|
|
||||||
|
from flask import Flask, render_template
|
||||||
|
|
||||||
|
def page_not_found(e):
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
def create_app(config_filename):
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.register_error_handler(404, page_not_found)
|
||||||
|
return app
|
||||||
|
|
||||||
An example template might be this:
|
An example template might be this:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
@ -80,4 +97,3 @@ An example template might be this:
|
||||||
<p>What you were looking for is just not there.
|
<p>What you were looking for is just not there.
|
||||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -49,5 +49,5 @@ web server's documentation.
|
||||||
See also
|
See also
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* The `Favicon <http://en.wikipedia.org/wiki/Favicon>`_ article on
|
* The `Favicon <https://en.wikipedia.org/wiki/Favicon>`_ article on
|
||||||
Wikipedia
|
Wikipedia
|
||||||
|
|
|
@ -21,7 +21,7 @@ specific upload folder and displays a file to the user. Let's look at the
|
||||||
bootstrapping code for our application::
|
bootstrapping code for our application::
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from flask import Flask, request, redirect, url_for
|
from flask import Flask, flash, request, redirect, url_for
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||||
|
@ -58,7 +58,7 @@ the file and redirects the user to the URL for the uploaded file::
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
# if user does not select file, browser also
|
# if user does not select file, browser also
|
||||||
# submit a empty part without filename
|
# submit an empty part without filename
|
||||||
if file.filename == '':
|
if file.filename == '':
|
||||||
flash('No selected file')
|
flash('No selected file')
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
|
@ -72,7 +72,7 @@ the file and redirects the user to the URL for the uploaded file::
|
||||||
<title>Upload new File</title>
|
<title>Upload new File</title>
|
||||||
<h1>Upload new File</h1>
|
<h1>Upload new File</h1>
|
||||||
<form method=post enctype=multipart/form-data>
|
<form method=post enctype=multipart/form-data>
|
||||||
<p><input type=file name=file>
|
<input type=file name=file>
|
||||||
<input type=submit value=Upload>
|
<input type=submit value=Upload>
|
||||||
</form>
|
</form>
|
||||||
'''
|
'''
|
||||||
|
@ -181,4 +181,4 @@ applications dealing with uploads, there is also a Flask extension called
|
||||||
blacklisting of extensions and more.
|
blacklisting of extensions and more.
|
||||||
|
|
||||||
.. _jQuery: https://jquery.com/
|
.. _jQuery: https://jquery.com/
|
||||||
.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/
|
.. _Flask-Uploads: https://pythonhosted.org/Flask-Uploads/
|
||||||
|
|
|
@ -8,15 +8,19 @@ module. That is quite simple. Imagine a small application looks like
|
||||||
this::
|
this::
|
||||||
|
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/yourapplication.py
|
yourapplication.py
|
||||||
/static
|
/static
|
||||||
/style.css
|
style.css
|
||||||
/templates
|
/templates
|
||||||
layout.html
|
layout.html
|
||||||
index.html
|
index.html
|
||||||
login.html
|
login.html
|
||||||
...
|
...
|
||||||
|
|
||||||
|
If you find yourself stuck on something, feel free
|
||||||
|
to take a look at the source code for this example.
|
||||||
|
You'll find `the full src for this example here`_.
|
||||||
|
|
||||||
Simple Packages
|
Simple Packages
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -29,9 +33,9 @@ You should then end up with something like that::
|
||||||
|
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/__init__.py
|
__init__.py
|
||||||
/static
|
/static
|
||||||
/style.css
|
style.css
|
||||||
/templates
|
/templates
|
||||||
layout.html
|
layout.html
|
||||||
index.html
|
index.html
|
||||||
|
@ -41,11 +45,36 @@ You should then end up with something like that::
|
||||||
But how do you run your application now? The naive ``python
|
But how do you run your application now? The naive ``python
|
||||||
yourapplication/__init__.py`` will not work. Let's just say that Python
|
yourapplication/__init__.py`` will not work. Let's just say that Python
|
||||||
does not want modules in packages to be the startup file. But that is not
|
does not want modules in packages to be the startup file. But that is not
|
||||||
a big problem, just add a new file called :file:`runserver.py` next to the inner
|
a big problem, just add a new file called :file:`setup.py` next to the inner
|
||||||
:file:`yourapplication` folder with the following contents::
|
:file:`yourapplication` folder with the following contents::
|
||||||
|
|
||||||
from yourapplication import app
|
from setuptools import setup
|
||||||
app.run(debug=True)
|
|
||||||
|
setup(
|
||||||
|
name='yourapplication',
|
||||||
|
packages=['yourapplication'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
In order to run the application you need to export an environment variable
|
||||||
|
that tells Flask where to find the application instance::
|
||||||
|
|
||||||
|
export FLASK_APP=yourapplication
|
||||||
|
|
||||||
|
If you are outside of the project directory make sure to provide the exact
|
||||||
|
path to your application directory. Similarly you can turn on "debug
|
||||||
|
mode" with this environment variable::
|
||||||
|
|
||||||
|
export FLASK_DEBUG=true
|
||||||
|
|
||||||
|
In order to install and run the application you need to issue the following
|
||||||
|
commands::
|
||||||
|
|
||||||
|
pip install -e .
|
||||||
|
flask run
|
||||||
|
|
||||||
What did we gain from this? Now we can restructure the application a bit
|
What did we gain from this? Now we can restructure the application a bit
|
||||||
into multiple modules. The only thing you have to remember is the
|
into multiple modules. The only thing you have to remember is the
|
||||||
|
@ -77,12 +106,12 @@ And this is what :file:`views.py` would look like::
|
||||||
You should then end up with something like that::
|
You should then end up with something like that::
|
||||||
|
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/runserver.py
|
setup.py
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/__init__.py
|
__init__.py
|
||||||
/views.py
|
views.py
|
||||||
/static
|
/static
|
||||||
/style.css
|
style.css
|
||||||
/templates
|
/templates
|
||||||
layout.html
|
layout.html
|
||||||
index.html
|
index.html
|
||||||
|
@ -105,6 +134,7 @@ You should then end up with something like that::
|
||||||
|
|
||||||
|
|
||||||
.. _working-with-modules:
|
.. _working-with-modules:
|
||||||
|
.. _the full src for this example here: https://github.com/pallets/flask/tree/master/examples/patterns/largerapp
|
||||||
|
|
||||||
Working with Blueprints
|
Working with Blueprints
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
|
@ -22,7 +22,7 @@ if you want to get started quickly.
|
||||||
You can download `Flask-SQLAlchemy`_ from `PyPI
|
You can download `Flask-SQLAlchemy`_ from `PyPI
|
||||||
<https://pypi.python.org/pypi/Flask-SQLAlchemy>`_.
|
<https://pypi.python.org/pypi/Flask-SQLAlchemy>`_.
|
||||||
|
|
||||||
.. _Flask-SQLAlchemy: http://pythonhosted.org/Flask-SQLAlchemy/
|
.. _Flask-SQLAlchemy: http://flask-sqlalchemy.pocoo.org/
|
||||||
|
|
||||||
|
|
||||||
Declarative
|
Declarative
|
||||||
|
@ -108,9 +108,9 @@ Querying is simple as well:
|
||||||
>>> User.query.filter(User.name == 'admin').first()
|
>>> User.query.filter(User.name == 'admin').first()
|
||||||
<User u'admin'>
|
<User u'admin'>
|
||||||
|
|
||||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
.. _declarative:
|
.. _declarative:
|
||||||
http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
||||||
|
|
||||||
Manual Object Relational Mapping
|
Manual Object Relational Mapping
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
@ -135,7 +135,7 @@ Here is an example :file:`database.py` module for your application::
|
||||||
def init_db():
|
def init_db():
|
||||||
metadata.create_all(bind=engine)
|
metadata.create_all(bind=engine)
|
||||||
|
|
||||||
As for the declarative approach you need to close the session after
|
As in the declarative approach, you need to close the session after
|
||||||
each request or application context shutdown. Put this into your
|
each request or application context shutdown. Put this into your
|
||||||
application module::
|
application module::
|
||||||
|
|
||||||
|
@ -215,4 +215,4 @@ You can also pass strings of SQL statements to the
|
||||||
(1, u'admin', u'admin@localhost')
|
(1, u'admin', u'admin@localhost')
|
||||||
|
|
||||||
For more information about SQLAlchemy, head over to the
|
For more information about SQLAlchemy, head over to the
|
||||||
`website <http://www.sqlalchemy.org/>`_.
|
`website <https://www.sqlalchemy.org/>`_.
|
||||||
|
|
|
@ -131,7 +131,7 @@ To pass variable parts to the SQL statement, use a question mark in the
|
||||||
statement and pass in the arguments as a list. Never directly add them to
|
statement and pass in the arguments as a list. Never directly add them to
|
||||||
the SQL statement with string formatting because this makes it possible
|
the SQL statement with string formatting because this makes it possible
|
||||||
to attack the application using `SQL Injections
|
to attack the application using `SQL Injections
|
||||||
<http://en.wikipedia.org/wiki/SQL_injection>`_.
|
<https://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||||
|
|
||||||
Initial Schemas
|
Initial Schemas
|
||||||
---------------
|
---------------
|
||||||
|
|
|
@ -19,7 +19,7 @@ forms.
|
||||||
fun. You can get it from `PyPI
|
fun. You can get it from `PyPI
|
||||||
<https://pypi.python.org/pypi/Flask-WTF>`_.
|
<https://pypi.python.org/pypi/Flask-WTF>`_.
|
||||||
|
|
||||||
.. _Flask-WTF: http://pythonhosted.org/Flask-WTF/
|
.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/stable/
|
||||||
|
|
||||||
The Forms
|
The Forms
|
||||||
---------
|
---------
|
||||||
|
|
|
@ -50,7 +50,14 @@ to tell your terminal the application to work with by exporting the
|
||||||
$ flask run
|
$ flask run
|
||||||
* Running on http://127.0.0.1:5000/
|
* Running on http://127.0.0.1:5000/
|
||||||
|
|
||||||
If you are on Windows you need to use ``set`` instead of ``export``.
|
If you are on Windows, the environment variable syntax depends on command line
|
||||||
|
interpreter. On Command Prompt::
|
||||||
|
|
||||||
|
C:\path\to\app>set FLASK_APP=hello.py
|
||||||
|
|
||||||
|
And on PowerShell::
|
||||||
|
|
||||||
|
PS C:\path\to\app> $env:FLASK_APP = "hello.py"
|
||||||
|
|
||||||
Alternatively you can use :command:`python -m flask`::
|
Alternatively you can use :command:`python -m flask`::
|
||||||
|
|
||||||
|
@ -102,9 +109,9 @@ docs to see the alternative method for running a server.
|
||||||
Invalid Import Name
|
Invalid Import Name
|
||||||
```````````````````
|
```````````````````
|
||||||
|
|
||||||
The ``-a`` argument to :command:`flask` is the name of the module to
|
The ``FLASK_APP`` environment variable is the name of the module to import at
|
||||||
import. In case that module is incorrectly named you will get an import
|
:command:`flask run`. In case that module is incorrectly named you will get an
|
||||||
error upon start (or if debug is enabled when you navigate to the
|
import error upon start (or if debug is enabled when you navigate to the
|
||||||
application). It will tell you what it tried to import and why it failed.
|
application). It will tell you what it tried to import and why it failed.
|
||||||
|
|
||||||
The most common reason is a typo or because you did not actually create an
|
The most common reason is a typo or because you did not actually create an
|
||||||
|
@ -153,20 +160,22 @@ Screenshot of the debugger in action:
|
||||||
:class: screenshot
|
:class: screenshot
|
||||||
:alt: screenshot of debugger in action
|
:alt: screenshot of debugger in action
|
||||||
|
|
||||||
|
More information on using the debugger can be found in the `Werkzeug
|
||||||
|
documentation`_.
|
||||||
|
|
||||||
|
.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/debug/#using-the-debugger
|
||||||
|
|
||||||
Have another debugger in mind? See :ref:`working-with-debuggers`.
|
Have another debugger in mind? See :ref:`working-with-debuggers`.
|
||||||
|
|
||||||
|
|
||||||
Routing
|
Routing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Modern web applications have beautiful URLs. This helps people remember
|
Modern web applications use meaningful URLs to help users. Users are more
|
||||||
the URLs, which is especially handy for applications that are used from
|
likely to like a page and come back if the page uses a meaningful URL they can
|
||||||
mobile devices with slower network connections. If the user can directly
|
remember and use to directly visit a page.
|
||||||
go to the desired page without having to hit the index page it is more
|
|
||||||
likely they will like the page and come back next time.
|
|
||||||
|
|
||||||
As you have seen above, the :meth:`~flask.Flask.route` decorator is used to
|
Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
|
||||||
bind a function to a URL. Here are some basic examples::
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
@ -176,16 +185,16 @@ bind a function to a URL. Here are some basic examples::
|
||||||
def hello():
|
def hello():
|
||||||
return 'Hello, World'
|
return 'Hello, World'
|
||||||
|
|
||||||
But there is more to it! You can make certain parts of the URL dynamic and
|
You can do more! You can make parts of the URL dynamic and attach multiple
|
||||||
attach multiple rules to a function.
|
rules to a function.
|
||||||
|
|
||||||
Variable Rules
|
Variable Rules
|
||||||
``````````````
|
``````````````
|
||||||
|
|
||||||
To add variable parts to a URL you can mark these special sections as
|
You can add variable sections to a URL by marking sections with
|
||||||
``<variable_name>``. Such a part is then passed as a keyword argument to your
|
``<variable_name>``. Your function then receives the ``<variable_name>``
|
||||||
function. Optionally a converter can be used by specifying a rule with
|
as a keyword argument. Optionally, you can use a converter to specify the type
|
||||||
``<converter:variable_name>``. Here are some nice examples::
|
of the argument like ``<converter:variable_name>``. ::
|
||||||
|
|
||||||
@app.route('/user/<username>')
|
@app.route('/user/<username>')
|
||||||
def show_user_profile(username):
|
def show_user_profile(username):
|
||||||
|
@ -197,24 +206,25 @@ function. Optionally a converter can be used by specifying a rule with
|
||||||
# show the post with the given id, the id is an integer
|
# show the post with the given id, the id is an integer
|
||||||
return 'Post %d' % post_id
|
return 'Post %d' % post_id
|
||||||
|
|
||||||
The following converters exist:
|
@app.route('/path/<path:subpath>')
|
||||||
|
def show_subpath(subpath):
|
||||||
|
# show the subpath after /path/
|
||||||
|
return 'Subpath %s' % subpath
|
||||||
|
|
||||||
=========== ===============================================
|
Converter types:
|
||||||
`string` accepts any text without a slash (the default)
|
|
||||||
`int` accepts integers
|
|
||||||
`float` like ``int`` but for floating point values
|
|
||||||
`path` like the default but also accepts slashes
|
|
||||||
`any` matches one of the items provided
|
|
||||||
`uuid` accepts UUID strings
|
|
||||||
=========== ===============================================
|
|
||||||
|
|
||||||
.. admonition:: Unique URLs / Redirection Behavior
|
========== ==========================================
|
||||||
|
``string`` (default) accepts any text without a slash
|
||||||
|
``int`` accepts positive integers
|
||||||
|
``float`` accepts positive floating point values
|
||||||
|
``path`` like ``string`` but also accepts slashes
|
||||||
|
``uuid`` accepts UUID strings
|
||||||
|
========== ==========================================
|
||||||
|
|
||||||
Flask's URL rules are based on Werkzeug's routing module. The idea
|
Unique URLs / Redirection Behavior
|
||||||
behind that module is to ensure beautiful and unique URLs based on
|
``````````````````````````````````
|
||||||
precedents laid down by Apache and earlier HTTP servers.
|
|
||||||
|
|
||||||
Take these two rules::
|
Take these two rules::
|
||||||
|
|
||||||
@app.route('/projects/')
|
@app.route('/projects/')
|
||||||
def projects():
|
def projects():
|
||||||
|
@ -224,84 +234,83 @@ The following converters exist:
|
||||||
def about():
|
def about():
|
||||||
return 'The about page'
|
return 'The about page'
|
||||||
|
|
||||||
Though they look rather similar, they differ in their use of the trailing
|
Though they look similar, they differ in their use of the trailing slash in
|
||||||
slash in the URL *definition*. In the first case, the canonical URL for the
|
the URL. In the first case, the canonical URL for the ``projects`` endpoint
|
||||||
``projects`` endpoint has a trailing slash. In that sense, it is similar to
|
uses a trailing slash. It's similar to a folder in a file system; if you
|
||||||
a folder on a filesystem. Accessing it without a trailing slash will cause
|
access the URL without a trailing slash, Flask redirects you to the
|
||||||
Flask to redirect to the canonical URL with the trailing slash.
|
canonical URL with the trailing slash.
|
||||||
|
|
||||||
In the second case, however, the URL is defined without a trailing slash,
|
In the second case, however, the URL definition lacks a trailing slash,
|
||||||
rather like the pathname of a file on UNIX-like systems. Accessing the URL
|
like the pathname of a file on UNIX-like systems. Accessing the URL with a
|
||||||
with a trailing slash will produce a 404 "Not Found" error.
|
trailing slash produces a 404 “Not Found” error.
|
||||||
|
|
||||||
This behavior allows relative URLs to continue working even if the trailing
|
|
||||||
slash is omitted, consistent with how Apache and other servers work. Also,
|
|
||||||
the URLs will stay unique, which helps search engines avoid indexing the
|
|
||||||
same page twice.
|
|
||||||
|
|
||||||
|
This behavior allows relative URLs to continue working even if the trailing
|
||||||
|
slash is omitted, consistent with how Apache and other servers work. Also,
|
||||||
|
the URLs will stay unique, which helps search engines avoid indexing the
|
||||||
|
same page twice.
|
||||||
|
|
||||||
.. _url-building:
|
.. _url-building:
|
||||||
|
|
||||||
URL Building
|
URL Building
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
If it can match URLs, can Flask also generate them? Of course it can. To
|
To build a URL to a specific function, use the :func:`~flask.url_for` function.
|
||||||
build a URL to a specific function you can use the :func:`~flask.url_for`
|
It accepts the name of the function as its first argument and any number of
|
||||||
function. It accepts the name of the function as first argument and a number
|
keyword arguments, each corresponding to a variable part of the URL rule.
|
||||||
of keyword arguments, each corresponding to the variable part of the URL rule.
|
Unknown variable parts are appended to the URL as query parameters.
|
||||||
Unknown variable parts are appended to the URL as query parameters. Here are
|
|
||||||
some examples::
|
Why would you want to build URLs using the URL reversing function
|
||||||
|
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
||||||
|
|
||||||
|
1. Reversing is often more descriptive than hard-coding the URLs.
|
||||||
|
2. You can change your URLs in one go instead of needing to remember to
|
||||||
|
manually change hard-coded URLs.
|
||||||
|
3. URL building handles escaping of special characters and Unicode data
|
||||||
|
transparently.
|
||||||
|
4. If your application is placed outside the URL root, for example, in
|
||||||
|
``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
|
||||||
|
handles that for you.
|
||||||
|
|
||||||
|
For example, here we use the :meth:`~flask.Flask.test_request_context` method
|
||||||
|
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
|
||||||
|
tells Flask to behave as though it's handling a request even while we use a
|
||||||
|
Python shell. See :ref:`context-locals`. ::
|
||||||
|
|
||||||
|
from flask import Flask, url_for
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'index'
|
||||||
|
|
||||||
|
@app.route('/login')
|
||||||
|
def login():
|
||||||
|
return 'login'
|
||||||
|
|
||||||
|
@app.route('/user/<username>')
|
||||||
|
def profile(username):
|
||||||
|
return '{}'s profile'.format(username)
|
||||||
|
|
||||||
|
with app.test_request_context():
|
||||||
|
print(url_for('index'))
|
||||||
|
print(url_for('login'))
|
||||||
|
print(url_for('login', next='/'))
|
||||||
|
print(url_for('profile', username='John Doe'))
|
||||||
|
|
||||||
>>> from flask import Flask, url_for
|
|
||||||
>>> app = Flask(__name__)
|
|
||||||
>>> @app.route('/')
|
|
||||||
... def index(): pass
|
|
||||||
...
|
|
||||||
>>> @app.route('/login')
|
|
||||||
... def login(): pass
|
|
||||||
...
|
|
||||||
>>> @app.route('/user/<username>')
|
|
||||||
... def profile(username): pass
|
|
||||||
...
|
|
||||||
>>> with app.test_request_context():
|
|
||||||
... print url_for('index')
|
|
||||||
... print url_for('login')
|
|
||||||
... print url_for('login', next='/')
|
|
||||||
... print url_for('profile', username='John Doe')
|
|
||||||
...
|
|
||||||
/
|
/
|
||||||
/login
|
/login
|
||||||
/login?next=/
|
/login?next=/
|
||||||
/user/John%20Doe
|
/user/John%20Doe
|
||||||
|
|
||||||
(This also uses the :meth:`~flask.Flask.test_request_context` method, explained
|
|
||||||
below. It tells Flask to behave as though it is handling a request, even
|
|
||||||
though we are interacting with it through a Python shell. Have a look at the
|
|
||||||
explanation below. :ref:`context-locals`).
|
|
||||||
|
|
||||||
Why would you want to build URLs using the URL reversing function
|
|
||||||
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
|
||||||
There are three good reasons for this:
|
|
||||||
|
|
||||||
1. Reversing is often more descriptive than hard-coding the URLs. More
|
|
||||||
importantly, it allows you to change URLs in one go, without having to
|
|
||||||
remember to change URLs all over the place.
|
|
||||||
2. URL building will handle escaping of special characters and Unicode
|
|
||||||
data transparently for you, so you don't have to deal with them.
|
|
||||||
3. If your application is placed outside the URL root - say, in
|
|
||||||
``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle
|
|
||||||
that properly for you.
|
|
||||||
|
|
||||||
|
|
||||||
HTTP Methods
|
HTTP Methods
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
HTTP (the protocol web applications are speaking) knows different methods for
|
Web applications use different HTTP methods when accessing URLs. You should
|
||||||
accessing URLs. By default, a route only answers to ``GET`` requests, but that
|
familiarize yourself with the HTTP methods as you work with Flask. By default,
|
||||||
can be changed by providing the ``methods`` argument to the
|
a route only answers to ``GET`` requests. You can use the ``methods`` argument
|
||||||
:meth:`~flask.Flask.route` decorator. Here are some examples::
|
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
|
||||||
|
::
|
||||||
from flask import request
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
|
@ -310,64 +319,11 @@ can be changed by providing the ``methods`` argument to the
|
||||||
else:
|
else:
|
||||||
show_the_login_form()
|
show_the_login_form()
|
||||||
|
|
||||||
If ``GET`` is present, ``HEAD`` will be added automatically for you. You
|
If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
|
||||||
don't have to deal with that. It will also make sure that ``HEAD`` requests
|
and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
|
||||||
are handled as the `HTTP RFC`_ (the document describing the HTTP
|
``OPTIONS`` is automatically implemented for you.
|
||||||
protocol) demands, so you can completely ignore that part of the HTTP
|
|
||||||
specification. Likewise, as of Flask 0.6, ``OPTIONS`` is implemented for you
|
|
||||||
automatically as well.
|
|
||||||
|
|
||||||
You have no idea what an HTTP method is? Worry not, here is a quick
|
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
|
||||||
introduction to HTTP methods and why they matter:
|
|
||||||
|
|
||||||
The HTTP method (also often called "the verb") tells the server what the
|
|
||||||
client wants to *do* with the requested page. The following methods are
|
|
||||||
very common:
|
|
||||||
|
|
||||||
``GET``
|
|
||||||
The browser tells the server to just *get* the information stored on
|
|
||||||
that page and send it. This is probably the most common method.
|
|
||||||
|
|
||||||
``HEAD``
|
|
||||||
The browser tells the server to get the information, but it is only
|
|
||||||
interested in the *headers*, not the content of the page. An
|
|
||||||
application is supposed to handle that as if a ``GET`` request was
|
|
||||||
received but to not deliver the actual content. In Flask you don't
|
|
||||||
have to deal with that at all, the underlying Werkzeug library handles
|
|
||||||
that for you.
|
|
||||||
|
|
||||||
``POST``
|
|
||||||
The browser tells the server that it wants to *post* some new
|
|
||||||
information to that URL and that the server must ensure the data is
|
|
||||||
stored and only stored once. This is how HTML forms usually
|
|
||||||
transmit data to the server.
|
|
||||||
|
|
||||||
``PUT``
|
|
||||||
Similar to ``POST`` but the server might trigger the store procedure
|
|
||||||
multiple times by overwriting the old values more than once. Now you
|
|
||||||
might be asking why this is useful, but there are some good reasons
|
|
||||||
to do it this way. Consider that the connection is lost during
|
|
||||||
transmission: in this situation a system between the browser and the
|
|
||||||
server might receive the request safely a second time without breaking
|
|
||||||
things. With ``POST`` that would not be possible because it must only
|
|
||||||
be triggered once.
|
|
||||||
|
|
||||||
``DELETE``
|
|
||||||
Remove the information at the given location.
|
|
||||||
|
|
||||||
``OPTIONS``
|
|
||||||
Provides a quick way for a client to figure out which methods are
|
|
||||||
supported by this URL. Starting with Flask 0.6, this is implemented
|
|
||||||
for you automatically.
|
|
||||||
|
|
||||||
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
|
||||||
form can submit to the server are ``GET`` and ``POST``. But with JavaScript
|
|
||||||
and future HTML standards you can use the other methods as well. Furthermore
|
|
||||||
HTTP has become quite popular lately and browsers are no longer the only
|
|
||||||
clients that are using HTTP. For instance, many revision control systems
|
|
||||||
use it.
|
|
||||||
|
|
||||||
.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt
|
|
||||||
|
|
||||||
Static Files
|
Static Files
|
||||||
------------
|
------------
|
||||||
|
@ -538,16 +494,16 @@ The Request Object
|
||||||
``````````````````
|
``````````````````
|
||||||
|
|
||||||
The request object is documented in the API section and we will not cover
|
The request object is documented in the API section and we will not cover
|
||||||
it here in detail (see :class:`~flask.request`). Here is a broad overview of
|
it here in detail (see :class:`~flask.Request`). Here is a broad overview of
|
||||||
some of the most common operations. First of all you have to import it from
|
some of the most common operations. First of all you have to import it from
|
||||||
the ``flask`` module::
|
the ``flask`` module::
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
The current request method is available by using the
|
The current request method is available by using the
|
||||||
:attr:`~flask.request.method` attribute. To access form data (data
|
:attr:`~flask.Request.method` attribute. To access form data (data
|
||||||
transmitted in a ``POST`` or ``PUT`` request) you can use the
|
transmitted in a ``POST`` or ``PUT`` request) you can use the
|
||||||
:attr:`~flask.request.form` attribute. Here is a full example of the two
|
:attr:`~flask.Request.form` attribute. Here is a full example of the two
|
||||||
attributes mentioned above::
|
attributes mentioned above::
|
||||||
|
|
||||||
@app.route('/login', methods=['POST', 'GET'])
|
@app.route('/login', methods=['POST', 'GET'])
|
||||||
|
@ -570,7 +526,7 @@ error page is shown instead. So for many situations you don't have to
|
||||||
deal with that problem.
|
deal with that problem.
|
||||||
|
|
||||||
To access parameters submitted in the URL (``?key=value``) you can use the
|
To access parameters submitted in the URL (``?key=value``) you can use the
|
||||||
:attr:`~flask.request.args` attribute::
|
:attr:`~flask.Request.args` attribute::
|
||||||
|
|
||||||
searchword = request.args.get('key', '')
|
searchword = request.args.get('key', '')
|
||||||
|
|
||||||
|
@ -579,7 +535,7 @@ We recommend accessing URL parameters with `get` or by catching the
|
||||||
bad request page in that case is not user friendly.
|
bad request page in that case is not user friendly.
|
||||||
|
|
||||||
For a full list of methods and attributes of the request object, head over
|
For a full list of methods and attributes of the request object, head over
|
||||||
to the :class:`~flask.request` documentation.
|
to the :class:`~flask.Request` documentation.
|
||||||
|
|
||||||
|
|
||||||
File Uploads
|
File Uploads
|
||||||
|
@ -817,6 +773,9 @@ values do not persist across requests, cookies are indeed enabled, and you are
|
||||||
not getting a clear error message, check the size of the cookie in your page
|
not getting a clear error message, check the size of the cookie in your page
|
||||||
responses compared to the size supported by web browsers.
|
responses compared to the size supported by web browsers.
|
||||||
|
|
||||||
|
Besides the default client-side based sessions, if you want to handle
|
||||||
|
sessions on the server-side instead, there are several
|
||||||
|
Flask extensions that support this.
|
||||||
|
|
||||||
Message Flashing
|
Message Flashing
|
||||||
----------------
|
----------------
|
||||||
|
|
|
@ -119,8 +119,8 @@ understand what is actually happening. The new behavior is quite simple:
|
||||||
not executed yet or at all (for example in test environments sometimes
|
not executed yet or at all (for example in test environments sometimes
|
||||||
you might want to not execute before-request callbacks).
|
you might want to not execute before-request callbacks).
|
||||||
|
|
||||||
Now what happens on errors? In production mode if an exception is not
|
Now what happens on errors? If you are not in debug mode and an exception is not
|
||||||
caught, the 500 internal server handler is called. In development mode
|
caught, the 500 internal server handler is called. In debug mode
|
||||||
however the exception is not further processed and bubbles up to the WSGI
|
however the exception is not further processed and bubbles up to the WSGI
|
||||||
server. That way things like the interactive debugger can provide helpful
|
server. That way things like the interactive debugger can provide helpful
|
||||||
debug information.
|
debug information.
|
||||||
|
@ -214,10 +214,11 @@ provide you with important information.
|
||||||
Starting with Flask 0.7 you have finer control over that behavior by
|
Starting with Flask 0.7 you have finer control over that behavior by
|
||||||
setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By
|
setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By
|
||||||
default it's linked to the setting of ``DEBUG``. If the application is in
|
default it's linked to the setting of ``DEBUG``. If the application is in
|
||||||
debug mode the context is preserved, in production mode it's not.
|
debug mode the context is preserved. If debug mode is set to off, the context
|
||||||
|
is not preserved.
|
||||||
|
|
||||||
Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` in production mode
|
Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` if debug mode is set to off
|
||||||
as it will cause your application to leak memory on exceptions. However
|
as it will cause your application to leak memory on exceptions. However,
|
||||||
it can be useful during development to get the same error preserving
|
it can be useful during development to get the same error preserving
|
||||||
behavior as in development mode when attempting to debug an error that
|
behavior as debug mode when attempting to debug an error that
|
||||||
only occurs under production settings.
|
only occurs under production settings.
|
||||||
|
|
|
@ -15,7 +15,7 @@ it JavaScript) into the context of a website. To remedy this, developers
|
||||||
have to properly escape text so that it cannot include arbitrary HTML
|
have to properly escape text so that it cannot include arbitrary HTML
|
||||||
tags. For more information on that have a look at the Wikipedia article
|
tags. For more information on that have a look at the Wikipedia article
|
||||||
on `Cross-Site Scripting
|
on `Cross-Site Scripting
|
||||||
<http://en.wikipedia.org/wiki/Cross-site_scripting>`_.
|
<https://en.wikipedia.org/wiki/Cross-site_scripting>`_.
|
||||||
|
|
||||||
Flask configures Jinja2 to automatically escape all values unless
|
Flask configures Jinja2 to automatically escape all values unless
|
||||||
explicitly told otherwise. This should rule out all XSS problems caused
|
explicitly told otherwise. This should rule out all XSS problems caused
|
||||||
|
@ -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
|
||||||
|
|
|
@ -27,7 +27,7 @@ executed in undefined order and do not modify any data.
|
||||||
|
|
||||||
The big advantage of signals over handlers is that you can safely
|
The big advantage of signals over handlers is that you can safely
|
||||||
subscribe to them for just a split second. These temporary
|
subscribe to them for just a split second. These temporary
|
||||||
subscriptions are helpful for unittesting for example. Say you want to
|
subscriptions are helpful for unit testing for example. Say you want to
|
||||||
know what templates were rendered as part of a request: signals allow you
|
know what templates were rendered as part of a request: signals allow you
|
||||||
to do exactly that.
|
to do exactly that.
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ signal. When you subscribe to a signal, be sure to also provide a sender
|
||||||
unless you really want to listen for signals from all applications. This is
|
unless you really want to listen for signals from all applications. This is
|
||||||
especially true if you are developing an extension.
|
especially true if you are developing an extension.
|
||||||
|
|
||||||
For example, here is a helper context manager that can be used in a unittest
|
For example, here is a helper context manager that can be used in a unit test
|
||||||
to determine which templates were rendered and what variables were passed
|
to determine which templates were rendered and what variables were passed
|
||||||
to the template::
|
to the template::
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,7 @@ Docstring conventions:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Module header:
|
Module header:
|
||||||
The module header consists of an utf-8 encoding declaration (if non
|
The module header consists of a utf-8 encoding declaration (if non
|
||||||
ASCII letters are used, but it is recommended all the time) and a
|
ASCII letters are used, but it is recommended all the time) and a
|
||||||
standard docstring::
|
standard docstring::
|
||||||
|
|
||||||
|
|
190
docs/testing.rst
190
docs/testing.rst
|
@ -5,23 +5,30 @@ Testing Flask Applications
|
||||||
|
|
||||||
**Something that is untested is broken.**
|
**Something that is untested is broken.**
|
||||||
|
|
||||||
The origin of this quote is unknown and while it is not entirely correct, it is also
|
The origin of this quote is unknown and while it is not entirely correct, it
|
||||||
not far from the truth. Untested applications make it hard to
|
is also not far from the truth. Untested applications make it hard to
|
||||||
improve existing code and developers of untested applications tend to
|
improve existing code and developers of untested applications tend to
|
||||||
become pretty paranoid. If an application has automated tests, you can
|
become pretty paranoid. If an application has automated tests, you can
|
||||||
safely make changes and instantly know if anything breaks.
|
safely make changes and instantly know if anything breaks.
|
||||||
|
|
||||||
Flask provides a way to test your application by exposing the Werkzeug
|
Flask provides a way to test your application by exposing the Werkzeug
|
||||||
test :class:`~werkzeug.test.Client` and handling the context locals for you.
|
test :class:`~werkzeug.test.Client` and handling the context locals for you.
|
||||||
You can then use that with your favourite testing solution. In this documentation
|
You can then use that with your favourite testing solution.
|
||||||
we will use the :mod:`unittest` package that comes pre-installed with Python.
|
|
||||||
|
In this documentation we will use the `pytest`_ package as the base
|
||||||
|
framework for our tests. You can install it with ``pip``, like so::
|
||||||
|
|
||||||
|
pip install pytest
|
||||||
|
|
||||||
|
.. _pytest:
|
||||||
|
https://pytest.org
|
||||||
|
|
||||||
The Application
|
The Application
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
First, we need an application to test; we will use the application from
|
First, we need an application to test; we will use the application from
|
||||||
the :ref:`tutorial`. If you don't have that application yet, get the
|
the :ref:`tutorial`. If you don't have that application yet, get the
|
||||||
sources from `the examples`_.
|
source code from `the examples`_.
|
||||||
|
|
||||||
.. _the examples:
|
.. _the examples:
|
||||||
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
||||||
|
@ -29,90 +36,91 @@ sources from `the examples`_.
|
||||||
The Testing Skeleton
|
The Testing Skeleton
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
In order to test the application, we add a second module
|
We begin by adding a tests directory under the application root. Then
|
||||||
(:file:`flaskr_tests.py`) and create a unittest skeleton there::
|
create a Python file to store our tests (:file:`test_flaskr.py`). When we
|
||||||
|
format the filename like ``test_*.py``, it will be auto-discoverable by
|
||||||
|
pytest.
|
||||||
|
|
||||||
|
Next, we create a `pytest fixture`_ called
|
||||||
|
:func:`client` that configures
|
||||||
|
the application for testing and initializes a new database.::
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import flaskr
|
|
||||||
import unittest
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
class FlaskrTestCase(unittest.TestCase):
|
import pytest
|
||||||
|
|
||||||
def setUp(self):
|
from flaskr import flaskr
|
||||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||||
flaskr.app.config['TESTING'] = True
|
flaskr.app.config['TESTING'] = True
|
||||||
self.app = flaskr.app.test_client()
|
client = flaskr.app.test_client()
|
||||||
|
|
||||||
with flaskr.app.app_context():
|
with flaskr.app.app_context():
|
||||||
flaskr.init_db()
|
flaskr.init_db()
|
||||||
|
|
||||||
def tearDown(self):
|
yield client
|
||||||
os.close(self.db_fd)
|
|
||||||
|
os.close(db_fd)
|
||||||
os.unlink(flaskr.app.config['DATABASE'])
|
os.unlink(flaskr.app.config['DATABASE'])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
This client fixture will be called by each individual test. It gives us a
|
||||||
unittest.main()
|
simple interface to the application, where we can trigger test requests to the
|
||||||
|
application. The client will also keep track of cookies for us.
|
||||||
|
|
||||||
The code in the :meth:`~unittest.TestCase.setUp` method creates a new test
|
During setup, the ``TESTING`` config flag is activated. What
|
||||||
client and initializes a new database. This function is called before
|
this does is disable error catching during request handling, so that
|
||||||
each individual test function is run. To delete the database after the
|
you get better error reports when performing test requests against the
|
||||||
test, we close the file and remove it from the filesystem in the
|
application.
|
||||||
:meth:`~unittest.TestCase.tearDown` method. Additionally during setup the
|
|
||||||
``TESTING`` config flag is activated. What it does is disable the error
|
|
||||||
catching during request handling so that you get better error reports when
|
|
||||||
performing test requests against the application.
|
|
||||||
|
|
||||||
This test client will give us a simple interface to the application. We can
|
Because SQLite3 is filesystem-based, we can easily use the :mod:`tempfile` module
|
||||||
trigger test requests to the application, and the client will also keep track
|
|
||||||
of cookies for us.
|
|
||||||
|
|
||||||
Because SQLite3 is filesystem-based we can easily use the tempfile module
|
|
||||||
to create a temporary database and initialize it. The
|
to create a temporary database and initialize it. The
|
||||||
:func:`~tempfile.mkstemp` function does two things for us: it returns a
|
:func:`~tempfile.mkstemp` function does two things for us: it returns a
|
||||||
low-level file handle and a random file name, the latter we use as
|
low-level file handle and a random file name, the latter we use as
|
||||||
database name. We just have to keep the `db_fd` around so that we can use
|
database name. We just have to keep the `db_fd` around so that we can use
|
||||||
the :func:`os.close` function to close the file.
|
the :func:`os.close` function to close the file.
|
||||||
|
|
||||||
|
To delete the database after the test, the fixture closes the file and removes
|
||||||
|
it from the filesystem.
|
||||||
|
|
||||||
If we now run the test suite, we should see the following output::
|
If we now run the test suite, we should see the following output::
|
||||||
|
|
||||||
$ python flaskr_tests.py
|
$ pytest
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
================ test session starts ================
|
||||||
Ran 0 tests in 0.000s
|
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
||||||
|
collected 0 items
|
||||||
|
|
||||||
OK
|
=========== no tests ran in 0.07 seconds ============
|
||||||
|
|
||||||
Even though it did not run any actual tests, we already know that our flaskr
|
Even though it did not run any actual tests, we already know that our ``flaskr``
|
||||||
application is syntactically valid, otherwise the import would have died
|
application is syntactically valid, otherwise the import would have died
|
||||||
with an exception.
|
with an exception.
|
||||||
|
|
||||||
|
.. _pytest fixture:
|
||||||
|
https://docs.pytest.org/en/latest/fixture.html
|
||||||
|
|
||||||
The First Test
|
The First Test
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Now it's time to start testing the functionality of the application.
|
Now it's time to start testing the functionality of the application.
|
||||||
Let's check that the application shows "No entries here so far" if we
|
Let's check that the application shows "No entries here so far" if we
|
||||||
access the root of the application (``/``). To do this, we add a new
|
access the root of the application (``/``). To do this, we add a new
|
||||||
test method to our class, like this::
|
test function to :file:`test_flaskr.py`, like this::
|
||||||
|
|
||||||
class FlaskrTestCase(unittest.TestCase):
|
def test_empty_db(client):
|
||||||
|
"""Start with a blank database."""
|
||||||
|
|
||||||
def setUp(self):
|
rv = client.get('/')
|
||||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
|
||||||
self.app = flaskr.app.test_client()
|
|
||||||
flaskr.init_db()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
os.close(self.db_fd)
|
|
||||||
os.unlink(flaskr.app.config['DATABASE'])
|
|
||||||
|
|
||||||
def test_empty_db(self):
|
|
||||||
rv = self.app.get('/')
|
|
||||||
assert b'No entries here so far' in rv.data
|
assert b'No entries here so far' in rv.data
|
||||||
|
|
||||||
Notice that our test functions begin with the word `test`; this allows
|
Notice that our test functions begin with the word `test`; this allows
|
||||||
:mod:`unittest` to automatically identify the method as a test to run.
|
`pytest`_ to automatically identify the function as a test to run.
|
||||||
|
|
||||||
By using `self.app.get` we can send an HTTP ``GET`` request to the application with
|
By using ``client.get`` we can send an HTTP ``GET`` request to the application with
|
||||||
the given path. The return value will be a :class:`~flask.Flask.response_class` object.
|
the given path. The return value will be a :class:`~flask.Flask.response_class` object.
|
||||||
We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
|
We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
|
||||||
the return value (as string) from the application. In this case, we ensure that
|
the return value (as string) from the application. In this case, we ensure that
|
||||||
|
@ -120,12 +128,15 @@ the return value (as string) from the application. In this case, we ensure that
|
||||||
|
|
||||||
Run it again and you should see one passing test::
|
Run it again and you should see one passing test::
|
||||||
|
|
||||||
$ python flaskr_tests.py
|
$ pytest -v
|
||||||
.
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
Ran 1 test in 0.034s
|
|
||||||
|
|
||||||
OK
|
================ test session starts ================
|
||||||
|
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
||||||
|
collected 1 items
|
||||||
|
|
||||||
|
tests/test_flaskr.py::test_empty_db PASSED
|
||||||
|
|
||||||
|
============= 1 passed in 0.10 seconds ==============
|
||||||
|
|
||||||
Logging In and Out
|
Logging In and Out
|
||||||
------------------
|
------------------
|
||||||
|
@ -136,67 +147,78 @@ of the application. To do this, we fire some requests to the login and logout
|
||||||
pages with the required form data (username and password). And because the
|
pages with the required form data (username and password). And because the
|
||||||
login and logout pages redirect, we tell the client to `follow_redirects`.
|
login and logout pages redirect, we tell the client to `follow_redirects`.
|
||||||
|
|
||||||
Add the following two methods to your `FlaskrTestCase` class::
|
Add the following two functions to your :file:`test_flaskr.py` file::
|
||||||
|
|
||||||
def login(self, username, password):
|
def login(client, username, password):
|
||||||
return self.app.post('/login', data=dict(
|
return client.post('/login', data=dict(
|
||||||
username=username,
|
username=username,
|
||||||
password=password
|
password=password
|
||||||
), follow_redirects=True)
|
), follow_redirects=True)
|
||||||
|
|
||||||
def logout(self):
|
|
||||||
return self.app.get('/logout', follow_redirects=True)
|
def logout(client):
|
||||||
|
return client.get('/logout', follow_redirects=True)
|
||||||
|
|
||||||
Now we can easily test that logging in and out works and that it fails with
|
Now we can easily test that logging in and out works and that it fails with
|
||||||
invalid credentials. Add this new test to the class::
|
invalid credentials. Add this new test function::
|
||||||
|
|
||||||
def test_login_logout(self):
|
def test_login_logout(client):
|
||||||
rv = self.login('admin', 'default')
|
"""Make sure login and logout works."""
|
||||||
assert 'You were logged in' in rv.data
|
|
||||||
rv = self.logout()
|
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
||||||
assert 'You were logged out' in rv.data
|
assert b'You were logged in' in rv.data
|
||||||
rv = self.login('adminx', 'default')
|
|
||||||
assert 'Invalid username' in rv.data
|
rv = logout(client)
|
||||||
rv = self.login('admin', 'defaultx')
|
assert b'You were logged out' in rv.data
|
||||||
assert 'Invalid password' in rv.data
|
|
||||||
|
rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD'])
|
||||||
|
assert b'Invalid username' in rv.data
|
||||||
|
|
||||||
|
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x')
|
||||||
|
assert b'Invalid password' in rv.data
|
||||||
|
|
||||||
Test Adding Messages
|
Test Adding Messages
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
We should also test that adding messages works. Add a new test method
|
We should also test that adding messages works. Add a new test function
|
||||||
like this::
|
like this::
|
||||||
|
|
||||||
def test_messages(self):
|
def test_messages(client):
|
||||||
self.login('admin', 'default')
|
"""Test that messages work."""
|
||||||
rv = self.app.post('/add', data=dict(
|
|
||||||
|
login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
||||||
|
rv = client.post('/add', data=dict(
|
||||||
title='<Hello>',
|
title='<Hello>',
|
||||||
text='<strong>HTML</strong> allowed here'
|
text='<strong>HTML</strong> allowed here'
|
||||||
), follow_redirects=True)
|
), follow_redirects=True)
|
||||||
assert 'No entries here so far' not in rv.data
|
assert b'No entries here so far' not in rv.data
|
||||||
assert '<Hello>' in rv.data
|
assert b'<Hello>' in rv.data
|
||||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
assert b'<strong>HTML</strong> allowed here' in rv.data
|
||||||
|
|
||||||
Here we check that HTML is allowed in the text but not in the title,
|
Here we check that HTML is allowed in the text but not in the title,
|
||||||
which is the intended behavior.
|
which is the intended behavior.
|
||||||
|
|
||||||
Running that should now give us three passing tests::
|
Running that should now give us three passing tests::
|
||||||
|
|
||||||
$ python flaskr_tests.py
|
$ pytest -v
|
||||||
...
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
Ran 3 tests in 0.332s
|
|
||||||
|
|
||||||
OK
|
================ test session starts ================
|
||||||
|
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
||||||
|
collected 3 items
|
||||||
|
|
||||||
|
tests/test_flaskr.py::test_empty_db PASSED
|
||||||
|
tests/test_flaskr.py::test_login_logout PASSED
|
||||||
|
tests/test_flaskr.py::test_messages PASSED
|
||||||
|
|
||||||
|
============= 3 passed in 0.23 seconds ==============
|
||||||
|
|
||||||
For more complex tests with headers and status codes, check out the
|
For more complex tests with headers and status codes, check out the
|
||||||
`MiniTwit Example`_ from the sources which contains a larger test
|
`MiniTwit Example`_ from the sources which contains a larger test
|
||||||
suite.
|
suite.
|
||||||
|
|
||||||
|
|
||||||
.. _MiniTwit Example:
|
.. _MiniTwit Example:
|
||||||
https://github.com/pallets/flask/tree/master/examples/minitwit/
|
https://github.com/pallets/flask/tree/master/examples/minitwit/
|
||||||
|
|
||||||
|
|
||||||
Other Testing Tricks
|
Other Testing Tricks
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.. _tutorial-css:
|
.. _tutorial-css:
|
||||||
|
|
||||||
Step 9: Adding Style
|
Step 8: Adding Style
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Now that everything else works, it's time to add some style to the
|
Now that everything else works, it's time to add some style to the
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
Step 4: Database Connections
|
Step 4: Database Connections
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
You now have a function for establishing a database connection with
|
Let's continue building our code in the ``flaskr.py`` file.
|
||||||
|
(Scroll to the end of the page for more about project layout.)
|
||||||
|
|
||||||
|
You currently have a function for establishing a database connection with
|
||||||
`connect_db`, but by itself, it is not particularly useful. Creating and
|
`connect_db`, but by itself, it is not particularly useful. Creating and
|
||||||
closing database connections all the time is very inefficient, so you will
|
closing database connections all the time is very inefficient, so you will
|
||||||
need to keep it around for longer. Because database connections
|
need to keep it around for longer. Because database connections
|
||||||
|
|
|
@ -9,33 +9,39 @@ systems need a schema that tells them how to store that information.
|
||||||
Before starting the server for the first time, it's important to create
|
Before starting the server for the first time, it's important to create
|
||||||
that schema.
|
that schema.
|
||||||
|
|
||||||
Such a schema can be created by piping the ``schema.sql`` file into the
|
Such a schema could be created by piping the ``schema.sql`` file into the
|
||||||
`sqlite3` command as follows::
|
``sqlite3`` command as follows::
|
||||||
|
|
||||||
sqlite3 /tmp/flaskr.db < schema.sql
|
sqlite3 /tmp/flaskr.db < schema.sql
|
||||||
|
|
||||||
The downside of this is that it requires the ``sqlite3`` command to be
|
However, the downside of this is that it requires the ``sqlite3`` command
|
||||||
installed, which is not necessarily the case on every system. This also
|
to be installed, which is not necessarily the case on every system. This
|
||||||
requires that you provide the path to the database, which can introduce
|
also requires that you provide the path to the database, which can introduce
|
||||||
errors. It's a good idea to add a function that initializes the database
|
errors.
|
||||||
for you, to the application.
|
|
||||||
|
|
||||||
To do this, you can create a function and hook it into a :command:`flask`
|
Instead of the ``sqlite3`` command above, it's a good idea to add a function
|
||||||
command that initializes the database. For now just take a look at the
|
to our application that initializes the database for you. To do this, you
|
||||||
code segment below. A good place to add this function, and command, is
|
can create a function and hook it into a :command:`flask` command that
|
||||||
just below the `connect_db` function in :file:`flaskr.py`::
|
initializes the database.
|
||||||
|
|
||||||
|
Take a look at the code segment below. A good place to add this function,
|
||||||
|
and command, is just below the ``connect_db`` function in :file:`flaskr.py`::
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
||||||
with app.open_resource('schema.sql', mode='r') as f:
|
with 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')
|
@app.cli.command('initdb')
|
||||||
def initdb_command():
|
def initdb_command():
|
||||||
"""Initializes the database."""
|
"""Initializes the database."""
|
||||||
|
|
||||||
init_db()
|
init_db()
|
||||||
print 'Initialized the database.'
|
print('Initialized the database.')
|
||||||
|
|
||||||
The ``app.cli.command()`` decorator registers a new command with the
|
The ``app.cli.command()`` decorator registers a new command with the
|
||||||
:command:`flask` script. When the command executes, Flask will automatically
|
:command:`flask` script. When the command executes, Flask will automatically
|
||||||
|
@ -59,7 +65,8 @@ On that cursor, there is a method to execute a complete script. Finally, you
|
||||||
only have to commit the changes. SQLite3 and other transactional
|
only have to commit the changes. SQLite3 and other transactional
|
||||||
databases will not commit unless you explicitly tell it to.
|
databases will not commit unless you explicitly tell it to.
|
||||||
|
|
||||||
Now, it is possible to create a database with the :command:`flask` script::
|
Now, in a terminal, from the application root directory :file:`flaskr/` it is
|
||||||
|
possible to create a database with the :command:`flask` script::
|
||||||
|
|
||||||
flask initdb
|
flask initdb
|
||||||
Initialized the database.
|
Initialized the database.
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
Step 0: Creating The Folders
|
Step 0: Creating The Folders
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Before getting started, you will need to create the folders needed for this
|
It is recommended to install your Flask application within a virtualenv. Please
|
||||||
application::
|
read the :ref:`installation` section to set up your environment.
|
||||||
|
|
||||||
|
Now that you have installed Flask, you will need to create the folders required
|
||||||
|
for this tutorial. Your directory structure will look like this::
|
||||||
|
|
||||||
/flaskr
|
/flaskr
|
||||||
/flaskr
|
/flaskr
|
||||||
|
@ -13,13 +16,14 @@ application::
|
||||||
|
|
||||||
The application will be installed and run as Python package. This is the
|
The application will be installed and run as Python package. This is the
|
||||||
recommended way to install and run Flask applications. You will see exactly
|
recommended way to install and run Flask applications. You will see exactly
|
||||||
how to run ``flaskr`` later on in this tutorial. For now go ahead and create
|
how to run ``flaskr`` later on in this tutorial.
|
||||||
the applications directory structure. In the next few steps you will be
|
|
||||||
creating the database schema as well as the main module.
|
For now go ahead and create the applications directory structure. In the next
|
||||||
|
few steps you will be creating the database schema as well as the main module.
|
||||||
|
|
||||||
As a quick side note, the files inside of the :file:`static` folder are
|
As a quick side note, the files inside of the :file:`static` folder are
|
||||||
available to users of the application via HTTP. This is the place where CSS and
|
available to users of the application via HTTP. This is the place where CSS and
|
||||||
Javascript files go. Inside the :file:`templates` folder, Flask will look for
|
JavaScript files go. Inside the :file:`templates` folder, Flask will look for
|
||||||
`Jinja2`_ templates. You will see examples of this later on.
|
`Jinja2`_ templates. You will see examples of this later on.
|
||||||
|
|
||||||
For now you should continue with :ref:`tutorial-schema`.
|
For now you should continue with :ref:`tutorial-schema`.
|
||||||
|
|
|
@ -3,19 +3,19 @@
|
||||||
Tutorial
|
Tutorial
|
||||||
========
|
========
|
||||||
|
|
||||||
You want to develop an application with Python and Flask? Here you have
|
Learn by example to develop an application with Python and Flask.
|
||||||
the chance to learn by example. In this tutorial, we will create a simple
|
|
||||||
microblogging application. It only supports one user that can create
|
In this tutorial, we will create a simple blogging application. It only
|
||||||
text-only entries and there are no feeds or comments, but it still
|
supports one user, only allows text entries, and has no feeds or comments.
|
||||||
features everything you need to get started. We will use Flask and SQLite
|
|
||||||
as a database (which comes out of the box with Python) so there is nothing
|
While very simple, this example still features everything you need to get
|
||||||
else you need.
|
started. In addition to Flask, we will use SQLite for the database, which is
|
||||||
|
built-in to Python, so there is nothing else you need.
|
||||||
|
|
||||||
If you want the full source code in advance or for comparison, check out
|
If you want the full source code in advance or for comparison, check out
|
||||||
the `example source`_.
|
the `example source`_.
|
||||||
|
|
||||||
.. _example source:
|
.. _example source: https://github.com/pallets/flask/tree/master/examples/flaskr/
|
||||||
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
@ -24,7 +24,7 @@ the `example source`_.
|
||||||
folders
|
folders
|
||||||
schema
|
schema
|
||||||
setup
|
setup
|
||||||
setuptools
|
packaging
|
||||||
dbcon
|
dbcon
|
||||||
dbinit
|
dbinit
|
||||||
views
|
views
|
||||||
|
|
|
@ -22,7 +22,7 @@ connections in a more intelligent way, allowing you to target different
|
||||||
relational databases at once and more. You might also want to consider
|
relational databases at once and more. You might also want to consider
|
||||||
one of the popular NoSQL databases if your data is more suited for those.
|
one of the popular NoSQL databases if your data is more suited for those.
|
||||||
|
|
||||||
Here a screenshot of the final application:
|
Here is a screenshot of the final application:
|
||||||
|
|
||||||
.. image:: ../_static/flaskr.png
|
.. image:: ../_static/flaskr.png
|
||||||
:align: center
|
:align: center
|
||||||
|
@ -31,4 +31,4 @@ Here a screenshot of the final application:
|
||||||
|
|
||||||
Continue with :ref:`tutorial-folders`.
|
Continue with :ref:`tutorial-folders`.
|
||||||
|
|
||||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.. _tutorial-setuptools:
|
.. _tutorial-packaging:
|
||||||
|
|
||||||
Step 3: Installing flaskr with setuptools
|
Step 3: Installing flaskr as a Package
|
||||||
=========================================
|
======================================
|
||||||
|
|
||||||
Flask is now shipped with built-in support for `Click`_. Click provides
|
Flask is now shipped with built-in support for `Click`_. Click provides
|
||||||
Flask with enhanced and extensible command line utilities. Later in this
|
Flask with enhanced and extensible command line utilities. Later in this
|
||||||
|
@ -9,21 +9,23 @@ tutorial you will see exactly how to extend the ``flask`` command line
|
||||||
interface (CLI).
|
interface (CLI).
|
||||||
|
|
||||||
A useful pattern to manage a Flask application is to install your app
|
A useful pattern to manage a Flask application is to install your app
|
||||||
using `setuptools`_. This involves creating a :file:`setup.py`
|
following the `Python Packaging Guide`_. Presently this involves
|
||||||
in the projects root directory. You also need to add an empty
|
creating two new files; :file:`setup.py` and :file:`MANIFEST.in` in the
|
||||||
:file:`__init__.py` file to make the :file:`flaskr/flaskr` directory
|
projects root directory. You also need to add an :file:`__init__.py`
|
||||||
a package. The code structure at this point should be::
|
file to make the :file:`flaskr/flaskr` directory a package. After these
|
||||||
|
changes, your code structure should be::
|
||||||
|
|
||||||
/flaskr
|
/flaskr
|
||||||
/flaskr
|
/flaskr
|
||||||
__init__.py
|
__init__.py
|
||||||
/static
|
/static
|
||||||
/templates
|
/templates
|
||||||
|
flaskr.py
|
||||||
|
schema.sql
|
||||||
setup.py
|
setup.py
|
||||||
|
MANIFEST.in
|
||||||
|
|
||||||
The content of the ``setup.py`` file for ``flaskr`` is:
|
Create the ``setup.py`` file for ``flaskr`` with the following content::
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
@ -39,38 +41,53 @@ The content of the ``setup.py`` file for ``flaskr`` is:
|
||||||
When using setuptools, it is also necessary to specify any special files
|
When using setuptools, it is also necessary to specify any special files
|
||||||
that should be included in your package (in the :file:`MANIFEST.in`).
|
that should be included in your package (in the :file:`MANIFEST.in`).
|
||||||
In this case, the static and templates directories need to be included,
|
In this case, the static and templates directories need to be included,
|
||||||
as well as the schema. Create the :file:`MANIFEST.in` and add the
|
as well as the schema.
|
||||||
following lines::
|
|
||||||
|
Create the :file:`MANIFEST.in` and add the following lines::
|
||||||
|
|
||||||
graft flaskr/templates
|
graft flaskr/templates
|
||||||
graft flaskr/static
|
graft flaskr/static
|
||||||
include flaskr/schema.sql
|
include flaskr/schema.sql
|
||||||
|
|
||||||
|
Next, to simplify locating the application, create the file,
|
||||||
|
:file:`flaskr/__init__.py` containing only the following import statement::
|
||||||
|
|
||||||
|
from .flaskr import app
|
||||||
|
|
||||||
|
This import statement brings the application instance into the top-level
|
||||||
|
of the application package. When it is time to run the application, the
|
||||||
|
Flask development server needs the location of the app instance. This
|
||||||
|
import statement simplifies the location process. Without the above
|
||||||
|
import statement, the export statement a few steps below would need to be
|
||||||
|
``export FLASK_APP=flaskr.flaskr``.
|
||||||
|
|
||||||
At this point you should be able to install the application. As usual, it
|
At this point you should be able to install the application. As usual, it
|
||||||
is recommended to install your Flask application within a `virtualenv`_.
|
is recommended to install your Flask application within a `virtualenv`_.
|
||||||
With that said, go ahead and install the application with::
|
With that said, from the ``flaskr/`` directory, go ahead and install the
|
||||||
|
application with::
|
||||||
|
|
||||||
pip install --editable .
|
pip install --editable .
|
||||||
|
|
||||||
.. note:: The above installation command assumes that it is run within the
|
The above installation command assumes that it is run within the projects
|
||||||
projects root directory, `flaskr/`. Also, the `editable` flag allows
|
root directory, ``flaskr/``. The ``editable`` flag allows editing
|
||||||
editing source code without having to reinstall the Flask app each time
|
source code without having to reinstall the Flask app each time you make
|
||||||
you make changes.
|
changes. The flaskr app is now installed in your virtualenv (see output
|
||||||
|
of ``pip freeze``).
|
||||||
|
|
||||||
With that out of the way, you should be able to start up the application.
|
With that out of the way, you should be able to start up the application.
|
||||||
Do this with the following commands::
|
Do this on Mac or Linux with the following commands in ``flaskr/``::
|
||||||
|
|
||||||
export FLASK_APP=flaskr.flaskr
|
export FLASK_APP=flaskr
|
||||||
export FLASK_DEBUG=1
|
export FLASK_DEBUG=true
|
||||||
flask run
|
flask run
|
||||||
|
|
||||||
(In case you are on Windows you need to use `set` instead of `export`).
|
(In case you are on Windows you need to use ``set`` instead of ``export``).
|
||||||
The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger.
|
The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger.
|
||||||
*Never leave debug mode activated in a production system*, because it will
|
*Never leave debug mode activated in a production system*, because it will
|
||||||
allow users to execute code on the server!
|
allow users to execute code on the server!
|
||||||
|
|
||||||
You will see a message telling you that server has started along with
|
You will see a message telling you that server has started along with
|
||||||
the address at which you can access it.
|
the address at which you can access it in a browser.
|
||||||
|
|
||||||
When you head over to the server in your browser, you will get a 404 error
|
When you head over to the server in your browser, you will get a 404 error
|
||||||
because we don't have any views yet. That will be addressed a little later,
|
because we don't have any views yet. That will be addressed a little later,
|
||||||
|
@ -85,5 +102,5 @@ but first, you should get the database working.
|
||||||
Continue with :ref:`tutorial-dbcon`.
|
Continue with :ref:`tutorial-dbcon`.
|
||||||
|
|
||||||
.. _Click: http://click.pocoo.org
|
.. _Click: http://click.pocoo.org
|
||||||
.. _setuptools: https://setuptools.readthedocs.io
|
.. _Python Packaging Guide: https://packaging.python.org
|
||||||
.. _virtualenv: https://virtualenv.pypa.io
|
.. _virtualenv: https://virtualenv.pypa.io
|
|
@ -3,27 +3,31 @@
|
||||||
Step 2: Application Setup Code
|
Step 2: Application Setup Code
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Now that the schema is in place, you can create the application module,
|
Next, we will create the application module, :file:`flaskr.py`. Just like the
|
||||||
:file:`flaskr.py`. This file should be placed inside of the
|
:file:`schema.sql` file you created in the previous step, this file should be
|
||||||
:file:`flaskr/flaskr` folder. The first several lines of code in the
|
placed inside of the :file:`flaskr/flaskr` folder.
|
||||||
application module are the needed import statements. After that there will be a
|
|
||||||
few lines of configuration code. For small applications like ``flaskr``, it is
|
For this tutorial, all the Python code we use will be put into this file
|
||||||
possible to drop the configuration directly into the module. However, a cleaner
|
(except for one line in ``__init__.py``, and any testing or optional files you
|
||||||
solution is to create a separate ``.ini`` or ``.py`` file, load that, and
|
decide to create).
|
||||||
import the values from there.
|
|
||||||
|
The first several lines of code in the application module are the needed import
|
||||||
|
statements. After that there will be a few lines of configuration code.
|
||||||
|
|
||||||
|
For small applications like ``flaskr``, it is possible to drop the configuration
|
||||||
|
directly into the module. However, a cleaner solution is to create a separate
|
||||||
|
``.py`` file, load that, and import the values from there.
|
||||||
|
|
||||||
Here are the import statements (in :file:`flaskr.py`)::
|
Here are the import statements (in :file:`flaskr.py`)::
|
||||||
|
|
||||||
# all the imports
|
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
|
||||||
render_template, flash
|
from flask import (Flask, request, session, g, redirect, url_for, abort,
|
||||||
|
render_template, flash)
|
||||||
|
|
||||||
The next couple lines will create the actual application instance and
|
The next couple lines will create the actual application instance and
|
||||||
initialize it with the config from the same file in :file:`flaskr.py`:
|
initialize it with the config from the same file in :file:`flaskr.py`::
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
app = Flask(__name__) # create the application instance :)
|
app = Flask(__name__) # create the application instance :)
|
||||||
app.config.from_object(__name__) # load config from this file , flaskr.py
|
app.config.from_object(__name__) # load config from this file , flaskr.py
|
||||||
|
@ -37,8 +41,8 @@ initialize it with the config from the same file in :file:`flaskr.py`:
|
||||||
))
|
))
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
The :class:`~flask.Config` object works similarly to a dictionary, so it can be
|
In the above code, the :class:`~flask.Config` object works similarly to a
|
||||||
updated with new values.
|
dictionary, so it can be updated with new values.
|
||||||
|
|
||||||
.. admonition:: Database Path
|
.. admonition:: Database Path
|
||||||
|
|
||||||
|
@ -58,15 +62,15 @@ updated with new values.
|
||||||
Usually, it is a good idea to load a separate, environment-specific
|
Usually, it is a good idea to load a separate, environment-specific
|
||||||
configuration file. Flask allows you to import multiple configurations and it
|
configuration file. Flask allows you to import multiple configurations and it
|
||||||
will use the setting defined in the last import. This enables robust
|
will use the setting defined in the last import. This enables robust
|
||||||
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this.
|
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve
|
||||||
|
this. ::
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
Simply define the environment variable :envvar:`FLASKR_SETTINGS` that points to
|
If you want to do this (not required for this tutorial) simply define the
|
||||||
a config file to be loaded. The silent switch just tells Flask to not complain
|
environment variable :envvar:`FLASKR_SETTINGS` that points to a config file
|
||||||
if no such environment key is set.
|
to be loaded. The silent switch just tells Flask to not complain if no such
|
||||||
|
environment key is set.
|
||||||
|
|
||||||
In addition to that, you can use the :meth:`~flask.Config.from_object`
|
In addition to that, you can use the :meth:`~flask.Config.from_object`
|
||||||
method on the config object and provide it with an import name of a
|
method on the config object and provide it with an import name of a
|
||||||
|
@ -76,22 +80,22 @@ that in all cases, only variable names that are uppercase are considered.
|
||||||
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
||||||
Choose that key wisely and as hard to guess and complex as possible.
|
Choose that key wisely and as hard to guess and complex as possible.
|
||||||
|
|
||||||
Lastly, you will add a method that allows for easy connections to the
|
Lastly, add a method that allows for easy connections to the specified
|
||||||
specified database. This can be used to open a connection on request and
|
database. ::
|
||||||
also from the interactive Python shell or a script. This will come in
|
|
||||||
handy later. You can create a simple database connection through SQLite and
|
|
||||||
then tell it to use the :class:`sqlite3.Row` object to represent rows.
|
|
||||||
This allows the rows to be treated as if they were dictionaries instead of
|
|
||||||
tuples.
|
|
||||||
|
|
||||||
.. sourcecode:: python
|
|
||||||
|
|
||||||
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(app.config['DATABASE'])
|
||||||
rv.row_factory = sqlite3.Row
|
rv.row_factory = sqlite3.Row
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
This can be used to open a connection on request and also from the
|
||||||
|
interactive Python shell or a script. This will come in handy later.
|
||||||
|
You can create a simple database connection through SQLite and then tell
|
||||||
|
it to use the :class:`sqlite3.Row` object to represent rows. This allows
|
||||||
|
the rows to be treated as if they were dictionaries instead of tuples.
|
||||||
|
|
||||||
In the next section you will see how to run the application.
|
In the next section you will see how to run the application.
|
||||||
|
|
||||||
Continue with :ref:`tutorial-setuptools`.
|
Continue with :ref:`tutorial-packaging`.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.. _tutorial-templates:
|
.. _tutorial-templates:
|
||||||
|
|
||||||
Step 8: The Templates
|
Step 7: The Templates
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Now it is time to start working on the templates. As you may have
|
Now it is time to start working on the templates. As you may have
|
||||||
|
@ -15,7 +15,8 @@ escaped with their XML equivalents.
|
||||||
We are also using template inheritance which makes it possible to reuse
|
We are also using template inheritance which makes it possible to reuse
|
||||||
the layout of the website in all pages.
|
the layout of the website in all pages.
|
||||||
|
|
||||||
Put the following templates into the :file:`templates` folder:
|
Create the follwing three HTML files and place them in the
|
||||||
|
:file:`templates` folder:
|
||||||
|
|
||||||
.. _Jinja2: http://jinja.pocoo.org/docs/templates
|
.. _Jinja2: http://jinja.pocoo.org/docs/templates
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ show_entries.html
|
||||||
This template extends the :file:`layout.html` template from above to display the
|
This template extends the :file:`layout.html` template from above to display the
|
||||||
messages. Note that the ``for`` loop iterates over the messages we passed
|
messages. Note that the ``for`` loop iterates over the messages we passed
|
||||||
in with the :func:`~flask.render_template` function. Notice that the form is
|
in with the :func:`~flask.render_template` function. Notice that the form is
|
||||||
configured to to submit to the `add_entry` view function and use ``POST`` as
|
configured to submit to the `add_entry` view function and use ``POST`` as
|
||||||
HTTP method:
|
HTTP method:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
@ -79,9 +80,9 @@ HTTP method:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul class=entries>
|
<ul class=entries>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><em>Unbelievable. No entries here so far</em>
|
<li><em>Unbelievable. No entries here so far</em></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -9,10 +9,10 @@ modifications in the future. The application above is used as a basic
|
||||||
example of how to perform unit testing in the :ref:`testing` section of the
|
example of how to perform unit testing in the :ref:`testing` section of the
|
||||||
documentation. Go there to see how easy it is to test Flask applications.
|
documentation. Go there to see how easy it is to test Flask applications.
|
||||||
|
|
||||||
Adding Tests to flaskr
|
Adding tests to flaskr
|
||||||
======================
|
----------------------
|
||||||
|
|
||||||
Assuming you have seen the testing section above and have either written
|
Assuming you have seen the :ref:`testing` section and have either written
|
||||||
your own tests for ``flaskr`` or have followed along with the examples
|
your own tests for ``flaskr`` or have followed along with the examples
|
||||||
provided, you might be wondering about ways to organize the project.
|
provided, you might be wondering about ways to organize the project.
|
||||||
|
|
||||||
|
@ -24,30 +24,38 @@ One possible and recommended project structure is::
|
||||||
static/
|
static/
|
||||||
templates/
|
templates/
|
||||||
tests/
|
tests/
|
||||||
context.py
|
|
||||||
test_flaskr.py
|
test_flaskr.py
|
||||||
setup.py
|
setup.py
|
||||||
MANIFEST.in
|
MANIFEST.in
|
||||||
|
|
||||||
For now go ahead a create the :file:`tests/` directory as well as the
|
For now go ahead a create the :file:`tests/` directory as well as the
|
||||||
:file:`context.py` and :file:`test_flaskr.py` files, if you haven't
|
:file:`test_flaskr.py` file.
|
||||||
already. The context file is used as an import helper. The contents
|
|
||||||
of that file are::
|
|
||||||
|
|
||||||
import sys, os
|
Running the tests
|
||||||
|
-----------------
|
||||||
|
|
||||||
basedir = os.path.dirname(os.path.abspath(__file__))
|
At this point you can run the tests. Here ``pytest`` will be used.
|
||||||
sys.path.insert(0, basedir + '/../')
|
|
||||||
|
|
||||||
from flaskr import flaskr
|
.. note:: Make sure that ``pytest`` is installed in the same virtualenv
|
||||||
|
as flaskr. Otherwise ``pytest`` test will not be able to import the
|
||||||
|
required components to test the application::
|
||||||
|
|
||||||
Testing + Setuptools
|
pip install -e .
|
||||||
====================
|
pip install pytest
|
||||||
|
|
||||||
One way to handle testing is to integrate it with ``setuptools``. All it
|
Run and watch the tests pass, within the top-level :file:`flaskr/`
|
||||||
requires is adding a couple of lines to the :file:`setup.py` file and
|
directory as::
|
||||||
creating a new file :file:`setup.cfg`. Go ahead and update the
|
|
||||||
:file:`setup.py` to contain::
|
pytest
|
||||||
|
|
||||||
|
Testing + setuptools
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
One way to handle testing is to integrate it with ``setuptools``. Here
|
||||||
|
that requires adding a couple of lines to the :file:`setup.py` file and
|
||||||
|
creating a new file :file:`setup.cfg`. One benefit of running the tests
|
||||||
|
this way is that you do not have to install ``pytest``. Go ahead and
|
||||||
|
update the :file:`setup.py` file to contain::
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
@ -58,7 +66,6 @@ creating a new file :file:`setup.cfg`. Go ahead and update the
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'flask',
|
'flask',
|
||||||
],
|
],
|
||||||
)
|
|
||||||
setup_requires=[
|
setup_requires=[
|
||||||
'pytest-runner',
|
'pytest-runner',
|
||||||
],
|
],
|
||||||
|
@ -66,6 +73,7 @@ creating a new file :file:`setup.cfg`. Go ahead and update the
|
||||||
'pytest',
|
'pytest',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
Now create :file:`setup.cfg` in the project root (alongside
|
Now create :file:`setup.cfg` in the project root (alongside
|
||||||
:file:`setup.py`)::
|
:file:`setup.py`)::
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
.. _tutorial-views:
|
.. _tutorial-views:
|
||||||
|
|
||||||
Step 7: The View Functions
|
Step 6: The View Functions
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Now that the database connections are working, you can start writing the
|
Now that the database connections are working, you can start writing the
|
||||||
view functions. You will need four of them:
|
view functions. You will need four of them; Show Entries, Add New Entry,
|
||||||
|
Login and Logout. Add the following code snipets to :file:`flaskr.py`.
|
||||||
|
|
||||||
Show Entries
|
Show Entries
|
||||||
------------
|
------------
|
||||||
|
|
|
@ -19,7 +19,45 @@ providing the ``--upgrade`` parameter::
|
||||||
|
|
||||||
$ pip install --upgrade Flask
|
$ pip install --upgrade Flask
|
||||||
|
|
||||||
.. _upgrading-to-10:
|
.. _upgrading-to-012:
|
||||||
|
|
||||||
|
Version 0.12
|
||||||
|
------------
|
||||||
|
|
||||||
|
Changes to send_file
|
||||||
|
````````````````````
|
||||||
|
|
||||||
|
The ``filename`` is no longer automatically inferred from file-like objects.
|
||||||
|
This means that the following code will no longer automatically have
|
||||||
|
``X-Sendfile`` support, etag generation or MIME-type guessing::
|
||||||
|
|
||||||
|
response = send_file(open('/path/to/file.txt'))
|
||||||
|
|
||||||
|
Any of the following is functionally equivalent::
|
||||||
|
|
||||||
|
fname = '/path/to/file.txt'
|
||||||
|
|
||||||
|
# Just pass the filepath directly
|
||||||
|
response = send_file(fname)
|
||||||
|
|
||||||
|
# Set the MIME-type and ETag explicitly
|
||||||
|
response = send_file(open(fname), mimetype='text/plain')
|
||||||
|
response.set_etag(...)
|
||||||
|
|
||||||
|
# Set `attachment_filename` for MIME-type guessing
|
||||||
|
# ETag still needs to be manually set
|
||||||
|
response = send_file(open(fname), attachment_filename=fname)
|
||||||
|
response.set_etag(...)
|
||||||
|
|
||||||
|
The reason for this is that some file-like objects have an invalid or even
|
||||||
|
misleading ``name`` attribute. Silently swallowing errors in such cases was not
|
||||||
|
a satisfying solution.
|
||||||
|
|
||||||
|
Additionally the default of falling back to ``application/octet-stream`` has
|
||||||
|
been restricted. If Flask can't guess one or the user didn't provide one, the
|
||||||
|
function fails if no filename information was provided.
|
||||||
|
|
||||||
|
.. _upgrading-to-011:
|
||||||
|
|
||||||
Version 0.11
|
Version 0.11
|
||||||
------------
|
------------
|
||||||
|
@ -105,7 +143,7 @@ when there is no request context yet but an application context. The old
|
||||||
``flask.Flask.request_globals_class`` attribute was renamed to
|
``flask.Flask.request_globals_class`` attribute was renamed to
|
||||||
:attr:`flask.Flask.app_ctx_globals_class`.
|
:attr:`flask.Flask.app_ctx_globals_class`.
|
||||||
|
|
||||||
.. _Flask-OldSessions: http://pythonhosted.org/Flask-OldSessions/
|
.. _Flask-OldSessions: https://pythonhosted.org/Flask-OldSessions/
|
||||||
|
|
||||||
Version 0.9
|
Version 0.9
|
||||||
-----------
|
-----------
|
||||||
|
@ -160,7 +198,7 @@ applications with Flask. Because we want to make upgrading as easy as
|
||||||
possible we tried to counter the problems arising from these changes by
|
possible we tried to counter the problems arising from these changes by
|
||||||
providing a script that can ease the transition.
|
providing a script that can ease the transition.
|
||||||
|
|
||||||
The script scans your whole application and generates an unified diff with
|
The script scans your whole application and generates a unified diff with
|
||||||
changes it assumes are safe to apply. However as this is an automated
|
changes it assumes are safe to apply. However as this is an automated
|
||||||
tool it won't be able to find all use cases and it might miss some. We
|
tool it won't be able to find all use cases and it might miss some. We
|
||||||
internally spread a lot of deprecation warnings all over the place to make
|
internally spread a lot of deprecation warnings all over the place to make
|
||||||
|
|
|
@ -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.flaskr
|
export FLASK_APP=flaskr.factory:create_app()
|
||||||
|
|
||||||
4. initialize the database with this command:
|
4. initialize the database with this command:
|
||||||
|
|
||||||
|
|
|
@ -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'))
|
|
@ -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,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',
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import sys, os
|
|
||||||
|
|
||||||
basedir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
sys.path.insert(0, basedir + '/../')
|
|
||||||
|
|
||||||
from flaskr import flaskr
|
|
|
@ -9,23 +9,38 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import pytest
|
||||||
|
from flaskr.factory import create_app
|
||||||
|
from flaskr.blueprints.flaskr import init_db
|
||||||
|
|
||||||
from context import flaskr
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client(request):
|
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,
|
||||||
|
'DB_FD': db_fd
|
||||||
|
}
|
||||||
|
|
||||||
|
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():
|
def teardown():
|
||||||
os.close(db_fd)
|
os.close(app.config['DB_FD'])
|
||||||
os.unlink(flaskr.app.config['DATABASE'])
|
os.unlink(app.config['DATABASE'])
|
||||||
request.addfinalizer(teardown)
|
request.addfinalizer(teardown)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
@ -48,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'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
minitwit.db
|
||||||
|
.eggs/
|
|
@ -0,0 +1,3 @@
|
||||||
|
graft minitwit/templates
|
||||||
|
graft minitwit/static
|
||||||
|
include minitwit/schema.sql
|
|
@ -14,15 +14,19 @@
|
||||||
export an MINITWIT_SETTINGS environment variable
|
export an MINITWIT_SETTINGS environment variable
|
||||||
pointing to a configuration file.
|
pointing to a configuration file.
|
||||||
|
|
||||||
2. tell flask about the right application:
|
2. install the app from the root of the project directory
|
||||||
|
|
||||||
|
pip install --editable .
|
||||||
|
|
||||||
|
3. tell flask about the right application:
|
||||||
|
|
||||||
export FLASK_APP=minitwit
|
export FLASK_APP=minitwit
|
||||||
|
|
||||||
2. fire up a shell and run this:
|
4. fire up a shell and run this:
|
||||||
|
|
||||||
flask initdb
|
flask initdb
|
||||||
|
|
||||||
3. now you can run minitwit:
|
5. now you can run minitwit:
|
||||||
|
|
||||||
flask run
|
flask run
|
||||||
|
|
||||||
|
@ -31,5 +35,5 @@
|
||||||
|
|
||||||
~ Is it tested?
|
~ Is it tested?
|
||||||
|
|
||||||
You betcha. Run the `test_minitwit.py` file to
|
You betcha. Run the `python setup.py test` file to
|
||||||
see the tests pass.
|
see the tests pass.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .minitwit import app
|
|
@ -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)
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ def format_datetime(timestamp):
|
||||||
|
|
||||||
def gravatar_url(email, size=80):
|
def gravatar_url(email, size=80):
|
||||||
"""Return the gravatar image for the given email address."""
|
"""Return the gravatar image for the given email address."""
|
||||||
return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
|
return 'https://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
|
||||||
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
|
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[aliases]
|
||||||
|
test=pytest
|
|
@ -0,0 +1,16 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='minitwit',
|
||||||
|
packages=['minitwit'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
setup_requires=[
|
||||||
|
'pytest-runner',
|
||||||
|
],
|
||||||
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
)
|
|
@ -9,24 +9,22 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import minitwit
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import pytest
|
import pytest
|
||||||
|
from minitwit import minitwit
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client(request):
|
def client():
|
||||||
db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp()
|
db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp()
|
||||||
client = minitwit.app.test_client()
|
client = minitwit.app.test_client()
|
||||||
with minitwit.app.app_context():
|
with minitwit.app.app_context():
|
||||||
minitwit.init_db()
|
minitwit.init_db()
|
||||||
|
|
||||||
def teardown():
|
yield client
|
||||||
"""Get rid of the database again after each test."""
|
|
||||||
os.close(db_fd)
|
os.close(db_fd)
|
||||||
os.unlink(minitwit.app.config['DATABASE'])
|
os.unlink(minitwit.app.config['DATABASE'])
|
||||||
request.addfinalizer(teardown)
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def register(client, username, password, password2=None, email=None):
|
def register(client, username, password, password2=None, email=None):
|
|
@ -0,0 +1,10 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='yourapplication',
|
||||||
|
packages=['yourapplication'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
from yourapplication import app
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
app.config['TESTING'] = True
|
||||||
|
client = app.test_client()
|
||||||
|
return client
|
||||||
|
|
||||||
|
def test_index(client):
|
||||||
|
rv = client.get('/')
|
||||||
|
assert b"Hello World!" in rv.data
|
|
@ -0,0 +1,4 @@
|
||||||
|
from flask import Flask
|
||||||
|
app = Flask('yourapplication')
|
||||||
|
|
||||||
|
import yourapplication.views
|
|
@ -0,0 +1,5 @@
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello World!'
|
|
@ -10,7 +10,7 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.11.2-dev'
|
__version__ = '0.13-dev'
|
||||||
|
|
||||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||||
# in the module but are exported as public interface.
|
# in the module but are exported as public interface.
|
||||||
|
@ -40,7 +40,7 @@ from .signals import signals_available, template_rendered, request_started, \
|
||||||
# it.
|
# it.
|
||||||
from . import json
|
from . import json
|
||||||
|
|
||||||
# This was the only thing that flask used to export at one point and it had
|
# This was the only thing that Flask used to export at one point and it had
|
||||||
# a more generic name.
|
# a more generic name.
|
||||||
jsonify = json.jsonify
|
jsonify = json.jsonify
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ if not PY2:
|
||||||
itervalues = lambda d: iter(d.values())
|
itervalues = lambda d: iter(d.values())
|
||||||
iteritems = lambda d: iter(d.items())
|
iteritems = lambda d: iter(d.items())
|
||||||
|
|
||||||
|
from inspect import getfullargspec as getargspec
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
def reraise(tp, value, tb=None):
|
def reraise(tp, value, tb=None):
|
||||||
|
@ -43,6 +44,7 @@ else:
|
||||||
itervalues = lambda d: d.itervalues()
|
itervalues = lambda d: d.itervalues()
|
||||||
iteritems = lambda d: d.iteritems()
|
iteritems = lambda d: d.iteritems()
|
||||||
|
|
||||||
|
from inspect import getargspec
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
|
|
||||||
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
||||||
|
|
472
flask/app.py
472
flask/app.py
|
@ -10,31 +10,32 @@
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from threading import Lock
|
import warnings
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import chain
|
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from collections import deque
|
from itertools import chain
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
from werkzeug.datastructures import ImmutableDict
|
from werkzeug.datastructures import ImmutableDict, Headers
|
||||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
from werkzeug.exceptions import BadRequest, HTTPException, \
|
||||||
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
InternalServerError, MethodNotAllowed, default_exceptions, \
|
||||||
MethodNotAllowed, BadRequest, default_exceptions
|
BadRequestKeyError
|
||||||
|
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
from . import cli, json
|
||||||
locked_cached_property, _endpoint_from_view_func, find_package, \
|
from ._compat import integer_types, reraise, string_types, text_type
|
||||||
get_debug_flag
|
from .config import Config, ConfigAttribute
|
||||||
from . import json, cli
|
from .ctx import AppContext, RequestContext, _AppCtxGlobals
|
||||||
from .wrappers import Request, Response
|
from .globals import _request_ctx_stack, g, request, session
|
||||||
from .config import ConfigAttribute, Config
|
from .helpers import _PackageBoundObject, \
|
||||||
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
_endpoint_from_view_func, find_package, get_debug_flag, \
|
||||||
from .globals import _request_ctx_stack, request, session, g
|
get_flashed_messages, locked_cached_property, url_for
|
||||||
from .sessions import SecureCookieSessionInterface
|
from .sessions import SecureCookieSessionInterface
|
||||||
|
from .signals import appcontext_tearing_down, got_request_exception, \
|
||||||
|
request_finished, request_started, request_tearing_down
|
||||||
from .templating import DispatchingJinjaLoader, Environment, \
|
from .templating import DispatchingJinjaLoader, Environment, \
|
||||||
_default_template_ctx_processor
|
_default_template_ctx_processor
|
||||||
from .signals import request_started, request_finished, got_request_exception, \
|
from .wrappers import Request, Response
|
||||||
request_tearing_down, appcontext_tearing_down
|
|
||||||
from ._compat import reraise, string_types, text_type, integer_types
|
|
||||||
|
|
||||||
# a lock used for logger initialization
|
# a lock used for logger initialization
|
||||||
_logger_lock = Lock()
|
_logger_lock = Lock()
|
||||||
|
@ -124,6 +125,9 @@ class Flask(_PackageBoundObject):
|
||||||
.. versionadded:: 0.11
|
.. versionadded:: 0.11
|
||||||
The `root_path` parameter was added.
|
The `root_path` parameter was added.
|
||||||
|
|
||||||
|
.. versionadded:: 0.13
|
||||||
|
The `host_matching` and `static_host` parameters were added.
|
||||||
|
|
||||||
:param import_name: the name of the application package
|
:param import_name: the name of the application package
|
||||||
:param static_url_path: can be used to specify a different path for the
|
:param static_url_path: can be used to specify a different path for the
|
||||||
static files on the web. Defaults to the name
|
static files on the web. Defaults to the name
|
||||||
|
@ -131,6 +135,11 @@ class Flask(_PackageBoundObject):
|
||||||
:param static_folder: the folder with static files that should be served
|
:param static_folder: the folder with static files that should be served
|
||||||
at `static_url_path`. Defaults to the ``'static'``
|
at `static_url_path`. Defaults to the ``'static'``
|
||||||
folder in the root path of the application.
|
folder in the root path of the application.
|
||||||
|
:param host_matching: sets the app's ``url_map.host_matching`` to the given
|
||||||
|
given value. Defaults to False.
|
||||||
|
:param static_host: the host to use when adding the static route. Defaults
|
||||||
|
to None. Required when using ``host_matching=True``
|
||||||
|
with a ``static_folder`` configured.
|
||||||
:param template_folder: the folder that contains the templates that should
|
:param template_folder: the folder that contains the templates that should
|
||||||
be used by the application. Defaults to
|
be used by the application. Defaults to
|
||||||
``'templates'`` folder in the root path of the
|
``'templates'`` folder in the root path of the
|
||||||
|
@ -213,7 +222,7 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
#: The testing flag. Set this to ``True`` to enable the test mode of
|
#: The testing flag. Set this to ``True`` to enable the test mode of
|
||||||
#: Flask extensions (and in the future probably also Flask itself).
|
#: Flask extensions (and in the future probably also Flask itself).
|
||||||
#: For example this might activate unittest helpers that have an
|
#: For example this might activate test helpers that have an
|
||||||
#: additional runtime cost which should not be enabled by default.
|
#: additional runtime cost which should not be enabled by default.
|
||||||
#:
|
#:
|
||||||
#: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
|
#: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
|
||||||
|
@ -300,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,
|
||||||
|
@ -309,13 +318,13 @@ 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',
|
||||||
'JSON_AS_ASCII': True,
|
'JSON_AS_ASCII': True,
|
||||||
'JSON_SORT_KEYS': True,
|
'JSON_SORT_KEYS': True,
|
||||||
'JSONIFY_PRETTYPRINT_REGULAR': True,
|
'JSONIFY_PRETTYPRINT_REGULAR': False,
|
||||||
'JSONIFY_MIMETYPE': 'application/json',
|
'JSONIFY_MIMETYPE': 'application/json',
|
||||||
'TEMPLATES_AUTO_RELOAD': None,
|
'TEMPLATES_AUTO_RELOAD': None,
|
||||||
})
|
})
|
||||||
|
@ -338,7 +347,8 @@ class Flask(_PackageBoundObject):
|
||||||
session_interface = SecureCookieSessionInterface()
|
session_interface = SecureCookieSessionInterface()
|
||||||
|
|
||||||
def __init__(self, import_name, static_path=None, static_url_path=None,
|
def __init__(self, import_name, static_path=None, static_url_path=None,
|
||||||
static_folder='static', template_folder='templates',
|
static_folder='static', static_host=None,
|
||||||
|
host_matching=False, template_folder='templates',
|
||||||
instance_path=None, instance_relative_config=False,
|
instance_path=None, instance_relative_config=False,
|
||||||
root_path=None):
|
root_path=None):
|
||||||
_PackageBoundObject.__init__(self, import_name,
|
_PackageBoundObject.__init__(self, import_name,
|
||||||
|
@ -392,7 +402,7 @@ class Flask(_PackageBoundObject):
|
||||||
#: is the class for the instance check and the second the error handler
|
#: is the class for the instance check and the second the error handler
|
||||||
#: function.
|
#: function.
|
||||||
#:
|
#:
|
||||||
#: To register a error handler, use the :meth:`errorhandler`
|
#: To register an error handler, use the :meth:`errorhandler`
|
||||||
#: decorator.
|
#: decorator.
|
||||||
self.error_handler_spec = {None: self._error_handlers}
|
self.error_handler_spec = {None: self._error_handlers}
|
||||||
|
|
||||||
|
@ -405,17 +415,16 @@ class Flask(_PackageBoundObject):
|
||||||
#: .. versionadded:: 0.9
|
#: .. versionadded:: 0.9
|
||||||
self.url_build_error_handlers = []
|
self.url_build_error_handlers = []
|
||||||
|
|
||||||
#: A dictionary with lists of functions that should be called at the
|
#: A dictionary with lists of functions that will be called at the
|
||||||
#: beginning of the request. The key of the dictionary is the name of
|
#: beginning of each request. The key of the dictionary is the name of
|
||||||
#: the blueprint this function is active for, ``None`` for all requests.
|
#: the blueprint this function is active for, or ``None`` for all
|
||||||
#: This can for example be used to open database connections or
|
#: requests. To register a function, use the :meth:`before_request`
|
||||||
#: getting hold of the currently logged in user. To register a
|
#: decorator.
|
||||||
#: function here, use the :meth:`before_request` decorator.
|
|
||||||
self.before_request_funcs = {}
|
self.before_request_funcs = {}
|
||||||
|
|
||||||
#: A lists of functions that should be called at the beginning of the
|
#: A list of functions that will be called at the beginning of the
|
||||||
#: first request to this instance. To register a function here, use
|
#: first request to this instance. To register a function, use the
|
||||||
#: the :meth:`before_first_request` decorator.
|
#: :meth:`before_first_request` decorator.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.8
|
#: .. versionadded:: 0.8
|
||||||
self.before_first_request_funcs = []
|
self.before_first_request_funcs = []
|
||||||
|
@ -447,12 +456,11 @@ class Flask(_PackageBoundObject):
|
||||||
#: .. versionadded:: 0.9
|
#: .. versionadded:: 0.9
|
||||||
self.teardown_appcontext_funcs = []
|
self.teardown_appcontext_funcs = []
|
||||||
|
|
||||||
#: A dictionary with lists of functions that can be used as URL
|
#: A dictionary with lists of functions that are called before the
|
||||||
#: value processor functions. Whenever a URL is built these functions
|
#: :attr:`before_request_funcs` functions. The key of the dictionary is
|
||||||
#: are called to modify the dictionary of values in place. The key
|
#: the name of the blueprint this function is active for, or ``None``
|
||||||
#: ``None`` here is used for application wide
|
#: for all requests. To register a function, use
|
||||||
#: callbacks, otherwise the key is the name of the blueprint.
|
#: :meth:`url_value_preprocessor`.
|
||||||
#: Each of these functions has the chance to modify the dictionary
|
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.7
|
#: .. versionadded:: 0.7
|
||||||
self.url_value_preprocessors = {}
|
self.url_value_preprocessors = {}
|
||||||
|
@ -519,26 +527,29 @@ class Flask(_PackageBoundObject):
|
||||||
#: def to_python(self, value):
|
#: def to_python(self, value):
|
||||||
#: return value.split(',')
|
#: return value.split(',')
|
||||||
#: def to_url(self, values):
|
#: def to_url(self, values):
|
||||||
#: return ','.join(BaseConverter.to_url(value)
|
#: return ','.join(super(ListConverter, self).to_url(value)
|
||||||
#: for value in values)
|
#: for value in values)
|
||||||
#:
|
#:
|
||||||
#: app = Flask(__name__)
|
#: app = Flask(__name__)
|
||||||
#: app.url_map.converters['list'] = ListConverter
|
#: app.url_map.converters['list'] = ListConverter
|
||||||
self.url_map = Map()
|
self.url_map = Map()
|
||||||
|
|
||||||
|
self.url_map.host_matching = host_matching
|
||||||
|
|
||||||
# tracks internally if the application already handled at least one
|
# tracks internally if the application already handled at least one
|
||||||
# request.
|
# request.
|
||||||
self._got_first_request = False
|
self._got_first_request = False
|
||||||
self._before_request_lock = Lock()
|
self._before_request_lock = Lock()
|
||||||
|
|
||||||
# register the static folder for the application. Do that even
|
# Add a static route using the provided static_url_path, static_host,
|
||||||
# if the folder does not exist. First of all it might be created
|
# and static_folder if there is a configured static_folder.
|
||||||
# while the server is running (usually happens during development)
|
# Note we do this without checking if static_folder exists.
|
||||||
# but also because google appengine stores static files somewhere
|
# For one, it might be created while the server is running (e.g. during
|
||||||
# else when mapped with the .yml file.
|
# development). Also, Google App Engine stores static files somewhere
|
||||||
if self.has_static_folder:
|
if self.has_static_folder:
|
||||||
|
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
|
||||||
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||||
endpoint='static',
|
endpoint='static', host=static_host,
|
||||||
view_func=self.send_static_file)
|
view_func=self.send_static_file)
|
||||||
|
|
||||||
#: The click command line context for this application. Commands
|
#: The click command line context for this application. Commands
|
||||||
|
@ -814,7 +825,8 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
:param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
|
:param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
|
||||||
have the server available externally as well. Defaults to
|
have the server available externally as well. Defaults to
|
||||||
``'127.0.0.1'``.
|
``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config
|
||||||
|
variable if present.
|
||||||
:param port: the port of the webserver. Defaults to ``5000`` or the
|
:param port: the port of the webserver. Defaults to ``5000`` or the
|
||||||
port defined in the ``SERVER_NAME`` config variable if
|
port defined in the ``SERVER_NAME`` config variable if
|
||||||
present.
|
present.
|
||||||
|
@ -825,25 +837,31 @@ class Flask(_PackageBoundObject):
|
||||||
:func:`werkzeug.serving.run_simple` for more
|
:func:`werkzeug.serving.run_simple` for more
|
||||||
information.
|
information.
|
||||||
"""
|
"""
|
||||||
|
# Change this into a no-op if the server is invoked from the
|
||||||
|
# command line. Have a look at cli.py for more information.
|
||||||
|
if os.environ.get('FLASK_RUN_FROM_CLI_SERVER') == '1':
|
||||||
|
from .debughelpers import explain_ignored_app_run
|
||||||
|
explain_ignored_app_run()
|
||||||
|
return
|
||||||
|
|
||||||
from werkzeug.serving import run_simple
|
from werkzeug.serving import run_simple
|
||||||
if host is None:
|
_host = '127.0.0.1'
|
||||||
host = '127.0.0.1'
|
_port = 5000
|
||||||
if port is None:
|
server_name = self.config.get("SERVER_NAME")
|
||||||
server_name = self.config['SERVER_NAME']
|
sn_host, sn_port = None, None
|
||||||
if server_name and ':' in server_name:
|
if server_name:
|
||||||
port = int(server_name.rsplit(':', 1)[1])
|
sn_host, _, sn_port = server_name.partition(':')
|
||||||
else:
|
host = host or sn_host or _host
|
||||||
port = 5000
|
port = int(port or sn_port or _port)
|
||||||
if debug is not None:
|
if debug is not None:
|
||||||
self.debug = bool(debug)
|
self.debug = bool(debug)
|
||||||
options.setdefault('use_reloader', self.debug)
|
options.setdefault('use_reloader', self.debug)
|
||||||
options.setdefault('use_debugger', self.debug)
|
options.setdefault('use_debugger', self.debug)
|
||||||
options.setdefault('passthrough_errors', True)
|
|
||||||
try:
|
try:
|
||||||
run_simple(host, port, self, **options)
|
run_simple(host, port, self, **options)
|
||||||
finally:
|
finally:
|
||||||
# reset the first request information if the development server
|
# reset the first request information if the development server
|
||||||
# resetted normally. This makes it possible to restart the server
|
# reset normally. This makes it possible to restart the server
|
||||||
# without reloader and that stuff from an interactive shell.
|
# without reloader and that stuff from an interactive shell.
|
||||||
self._got_first_request = False
|
self._got_first_request = False
|
||||||
|
|
||||||
|
@ -877,9 +895,9 @@ class Flask(_PackageBoundObject):
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
|
|
||||||
class CustomClient(FlaskClient):
|
class CustomClient(FlaskClient):
|
||||||
def __init__(self, authentication=None, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
FlaskClient.__init__(*args, **kwargs)
|
self._authentication = kwargs.pop("authentication")
|
||||||
self._authentication = authentication
|
super(CustomClient,self).__init__( *args, **kwargs)
|
||||||
|
|
||||||
app.test_client_class = CustomClient
|
app.test_client_class = CustomClient
|
||||||
client = app.test_client(authentication='Basic ....')
|
client = app.test_client(authentication='Basic ....')
|
||||||
|
@ -909,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):
|
||||||
|
@ -918,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
|
||||||
|
@ -960,7 +1005,7 @@ class Flask(_PackageBoundObject):
|
||||||
return iter(self._blueprint_order)
|
return iter(self._blueprint_order)
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
|
||||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||||
decorator. If a view_func is provided it will be registered with the
|
decorator. If a view_func is provided it will be registered with the
|
||||||
endpoint.
|
endpoint.
|
||||||
|
@ -1000,6 +1045,10 @@ class Flask(_PackageBoundObject):
|
||||||
endpoint
|
endpoint
|
||||||
:param view_func: the function to call when serving a request to the
|
:param view_func: the function to call when serving a request to the
|
||||||
provided endpoint
|
provided endpoint
|
||||||
|
:param provide_automatic_options: controls whether the ``OPTIONS``
|
||||||
|
method should be added automatically. This can also be controlled
|
||||||
|
by setting the ``view_func.provide_automatic_options = False``
|
||||||
|
before adding the rule.
|
||||||
:param options: the options to be forwarded to the underlying
|
:param options: the options to be forwarded to the underlying
|
||||||
:class:`~werkzeug.routing.Rule` object. A change
|
:class:`~werkzeug.routing.Rule` object. A change
|
||||||
to Werkzeug is handling of method options. methods
|
to Werkzeug is handling of method options. methods
|
||||||
|
@ -1029,6 +1078,7 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
# starting with Flask 0.8 the view_func object can disable and
|
# starting with Flask 0.8 the view_func object can disable and
|
||||||
# force-enable the automatic options handling.
|
# force-enable the automatic options handling.
|
||||||
|
if provide_automatic_options is None:
|
||||||
provide_automatic_options = getattr(view_func,
|
provide_automatic_options = getattr(view_func,
|
||||||
'provide_automatic_options', None)
|
'provide_automatic_options', None)
|
||||||
|
|
||||||
|
@ -1116,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 give a given
|
"""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)
|
||||||
|
@ -1129,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
|
||||||
|
@ -1154,13 +1191,15 @@ class Flask(_PackageBoundObject):
|
||||||
that do not necessarily have to be a subclass of the
|
that do not necessarily have to be a subclass of the
|
||||||
:class:`~werkzeug.exceptions.HTTPException` class.
|
:class:`~werkzeug.exceptions.HTTPException` class.
|
||||||
|
|
||||||
:param code: the code as integer for the handler
|
:param code_or_exception: the code as integer for the handler, or
|
||||||
|
an arbitrary exception
|
||||||
"""
|
"""
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
self._register_error_handler(None, code_or_exception, f)
|
self._register_error_handler(None, code_or_exception, f)
|
||||||
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
|
||||||
|
@ -1179,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)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
exc_class, code = self._get_exc_class_and_code(code_or_exception)
|
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
|
||||||
|
@ -1289,10 +1335,12 @@ class Flask(_PackageBoundObject):
|
||||||
def before_request(self, f):
|
def before_request(self, f):
|
||||||
"""Registers a function to run before each request.
|
"""Registers a function to run before each request.
|
||||||
|
|
||||||
The function will be called without any arguments.
|
For example, this can be used to open a database connection, or to load
|
||||||
If the function returns a non-None value, it's handled as
|
the logged in user from the session.
|
||||||
if it was the return value from the view and further
|
|
||||||
request handling is stopped.
|
The function will be called without any arguments. If it returns a
|
||||||
|
non-None value, the value is handled as if it was the return value from
|
||||||
|
the view, and further request handling is stopped.
|
||||||
"""
|
"""
|
||||||
self.before_request_funcs.setdefault(None, []).append(f)
|
self.before_request_funcs.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
@ -1348,7 +1396,7 @@ class Flask(_PackageBoundObject):
|
||||||
will have to surround the execution of these code by try/except
|
will have to surround the execution of these code by try/except
|
||||||
statements and log occurring errors.
|
statements and log occurring errors.
|
||||||
|
|
||||||
When a teardown function was called because of a exception it will
|
When a teardown function was called because of an exception it will
|
||||||
be passed an error object.
|
be passed an error object.
|
||||||
|
|
||||||
The return values of teardown functions are ignored.
|
The return values of teardown functions are ignored.
|
||||||
|
@ -1411,9 +1459,17 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def url_value_preprocessor(self, f):
|
def url_value_preprocessor(self, f):
|
||||||
"""Registers a function as URL value preprocessor for all view
|
"""Register a URL value preprocessor function for all view
|
||||||
functions of the application. It's called before the view functions
|
functions in the application. These functions will be called before the
|
||||||
are called and can modify the url values provided.
|
:meth:`before_request` functions.
|
||||||
|
|
||||||
|
The function can modify the values captured from the matched url before
|
||||||
|
they are passed to the view. For example, this can be used to pop a
|
||||||
|
common language code value and place it in ``g`` rather than pass it to
|
||||||
|
every view.
|
||||||
|
|
||||||
|
The function is passed the endpoint name and values dict. The return
|
||||||
|
value is ignored.
|
||||||
"""
|
"""
|
||||||
self.url_value_preprocessors.setdefault(None, []).append(f)
|
self.url_value_preprocessors.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
@ -1428,43 +1484,32 @@ class Flask(_PackageBoundObject):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def _find_error_handler(self, e):
|
def _find_error_handler(self, e):
|
||||||
"""Finds a registered error handler for the request’s blueprint.
|
"""Find a registered error handler for a request in this order:
|
||||||
Otherwise falls back to the app, returns None if not a suitable
|
blueprint handler for a specific code, app handler for a specific code,
|
||||||
handler is found.
|
blueprint generic HTTPException handler, app generic HTTPException handler,
|
||||||
|
and returns None if a suitable handler is not found.
|
||||||
"""
|
"""
|
||||||
exc_class, code = self._get_exc_class_and_code(type(e))
|
exc_class, code = self._get_exc_class_and_code(type(e))
|
||||||
|
|
||||||
def find_handler(handler_map):
|
def find_handler(handler_map):
|
||||||
if not handler_map:
|
if not handler_map:
|
||||||
return
|
return
|
||||||
queue = deque(exc_class.__mro__)
|
|
||||||
# Protect from geniuses who might create circular references in
|
|
||||||
# __mro__
|
|
||||||
done = set()
|
|
||||||
|
|
||||||
while queue:
|
for cls in exc_class.__mro__:
|
||||||
cls = queue.popleft()
|
|
||||||
if cls in done:
|
|
||||||
continue
|
|
||||||
done.add(cls)
|
|
||||||
handler = handler_map.get(cls)
|
handler = handler_map.get(cls)
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
# cache for next time exc_class is raised
|
# cache for next time exc_class is raised
|
||||||
handler_map[exc_class] = handler
|
handler_map[exc_class] = handler
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
queue.extend(cls.__mro__)
|
# check for any in blueprint or app
|
||||||
|
for name, c in ((request.blueprint, code), (None, code),
|
||||||
|
(request.blueprint, None), (None, None)):
|
||||||
|
handler = find_handler(self.error_handler_spec.get(name, {}).get(c))
|
||||||
|
|
||||||
# try blueprint handlers
|
if handler:
|
||||||
handler = find_handler(self.error_handler_spec
|
|
||||||
.get(request.blueprint, {})
|
|
||||||
.get(code))
|
|
||||||
if handler is not None:
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
# fall back to app handlers
|
|
||||||
return find_handler(self.error_handler_spec[None].get(code))
|
|
||||||
|
|
||||||
def handle_http_exception(self, e):
|
def handle_http_exception(self, e):
|
||||||
"""Handles an HTTP exception. By default this will invoke the
|
"""Handles an HTTP exception. By default this will invoke the
|
||||||
registered error handlers and fall back to returning the
|
registered error handlers and fall back to returning the
|
||||||
|
@ -1494,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):
|
||||||
|
@ -1510,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)
|
||||||
|
|
||||||
|
@ -1556,7 +1623,7 @@ class Flask(_PackageBoundObject):
|
||||||
self.log_exception((exc_type, exc_value, tb))
|
self.log_exception((exc_type, exc_value, tb))
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return InternalServerError()
|
return InternalServerError()
|
||||||
return handler(e)
|
return self.finalize_request(handler(e), from_error_handler=True)
|
||||||
|
|
||||||
def log_exception(self, exc_info):
|
def log_exception(self, exc_info):
|
||||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||||
|
@ -1624,9 +1691,30 @@ class Flask(_PackageBoundObject):
|
||||||
rv = self.dispatch_request()
|
rv = self.dispatch_request()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rv = self.handle_user_exception(e)
|
rv = self.handle_user_exception(e)
|
||||||
|
return self.finalize_request(rv)
|
||||||
|
|
||||||
|
def finalize_request(self, rv, from_error_handler=False):
|
||||||
|
"""Given the return value from a view function this finalizes
|
||||||
|
the request by converting it into a response and invoking the
|
||||||
|
postprocessing functions. This is invoked for both normal
|
||||||
|
request dispatching as well as error handlers.
|
||||||
|
|
||||||
|
Because this means that it might be called as a result of a
|
||||||
|
failure a special safe mode is available which can be enabled
|
||||||
|
with the `from_error_handler` flag. If enabled, failures in
|
||||||
|
response processing will be logged and otherwise ignored.
|
||||||
|
|
||||||
|
:internal:
|
||||||
|
"""
|
||||||
response = self.make_response(rv)
|
response = self.make_response(rv)
|
||||||
|
try:
|
||||||
response = self.process_response(response)
|
response = self.process_response(response)
|
||||||
request_finished.send(self, response=response)
|
request_finished.send(self, response=response)
|
||||||
|
except Exception:
|
||||||
|
if not from_error_handler:
|
||||||
|
raise
|
||||||
|
self.logger.exception('Request finalizing failed with an '
|
||||||
|
'error while handling an error')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def try_trigger_before_first_request_functions(self):
|
def try_trigger_before_first_request_functions(self):
|
||||||
|
@ -1679,62 +1767,106 @@ class Flask(_PackageBoundObject):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def make_response(self, rv):
|
def make_response(self, rv):
|
||||||
"""Converts the return value from a view function to a real
|
"""Convert the return value from a view function to an instance of
|
||||||
response object that is an instance of :attr:`response_class`.
|
:attr:`response_class`.
|
||||||
|
|
||||||
The following types are allowed for `rv`:
|
:param rv: the return value from the view function. The view function
|
||||||
|
must return a response. Returning ``None``, or the view ending
|
||||||
|
without returning, is not allowed. The following types are allowed
|
||||||
|
for ``view_rv``:
|
||||||
|
|
||||||
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
|
``str`` (``unicode`` in Python 2)
|
||||||
|
A response object is created with the string encoded to UTF-8
|
||||||
|
as the body.
|
||||||
|
|
||||||
======================= ===========================================
|
``bytes`` (``str`` in Python 2)
|
||||||
:attr:`response_class` the object is returned unchanged
|
A response object is created with the bytes as the body.
|
||||||
:class:`str` a response object is created with the
|
|
||||||
string as body
|
|
||||||
:class:`unicode` a response object is created with the
|
|
||||||
string encoded to utf-8 as body
|
|
||||||
a WSGI function the function is called as WSGI application
|
|
||||||
and buffered as response object
|
|
||||||
:class:`tuple` A tuple in the form ``(response, status,
|
|
||||||
headers)`` or ``(response, headers)``
|
|
||||||
where `response` is any of the
|
|
||||||
types defined here, `status` is a string
|
|
||||||
or an integer and `headers` is a list or
|
|
||||||
a dictionary with header values.
|
|
||||||
======================= ===========================================
|
|
||||||
|
|
||||||
:param rv: the return value from the view function
|
``tuple``
|
||||||
|
Either ``(body, status, headers)``, ``(body, status)``, or
|
||||||
|
``(body, headers)``, where ``body`` is any of the other types
|
||||||
|
allowed here, ``status`` is a string or an integer, and
|
||||||
|
``headers`` is a dictionary or a list of ``(key, value)``
|
||||||
|
tuples. If ``body`` is a :attr:`response_class` instance,
|
||||||
|
``status`` overwrites the exiting value and ``headers`` are
|
||||||
|
extended.
|
||||||
|
|
||||||
|
:attr:`response_class`
|
||||||
|
The object is returned unchanged.
|
||||||
|
|
||||||
|
other :class:`~werkzeug.wrappers.Response` class
|
||||||
|
The object is coerced to :attr:`response_class`.
|
||||||
|
|
||||||
|
:func:`callable`
|
||||||
|
The function is called as a WSGI application. The result is
|
||||||
|
used to create a response object.
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
.. versionchanged:: 0.9
|
||||||
Previously a tuple was interpreted as the arguments for the
|
Previously a tuple was interpreted as the arguments for the
|
||||||
response object.
|
response object.
|
||||||
"""
|
"""
|
||||||
status_or_headers = headers = None
|
|
||||||
if isinstance(rv, tuple):
|
|
||||||
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
|
|
||||||
|
|
||||||
|
status = headers = None
|
||||||
|
|
||||||
|
# unpack tuple returns
|
||||||
|
if isinstance(rv, (tuple, list)):
|
||||||
|
len_rv = len(rv)
|
||||||
|
|
||||||
|
# a 3-tuple is unpacked directly
|
||||||
|
if len_rv == 3:
|
||||||
|
rv, status, headers = rv
|
||||||
|
# decide if a 2-tuple has status or headers
|
||||||
|
elif len_rv == 2:
|
||||||
|
if isinstance(rv[1], (Headers, dict, tuple, list)):
|
||||||
|
rv, headers = rv
|
||||||
|
else:
|
||||||
|
rv, status = rv
|
||||||
|
# other sized tuples are not allowed
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
'The view function did not return a valid response tuple.'
|
||||||
|
' The tuple must have the form (body, status, headers),'
|
||||||
|
' (body, status), or (body, headers).'
|
||||||
|
)
|
||||||
|
|
||||||
|
# the body must not be None
|
||||||
if rv is None:
|
if rv is None:
|
||||||
raise ValueError('View function did not return a response')
|
raise TypeError(
|
||||||
|
'The view function did not return a valid response. The'
|
||||||
if isinstance(status_or_headers, (dict, list)):
|
' function either returned None or ended without a return'
|
||||||
headers, status_or_headers = status_or_headers, None
|
' statement.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# make sure the body is an instance of the response class
|
||||||
if not isinstance(rv, self.response_class):
|
if not isinstance(rv, self.response_class):
|
||||||
# When we create a response object directly, we let the constructor
|
|
||||||
# set the headers and status. We do this because there can be
|
|
||||||
# some extra logic involved when creating these objects with
|
|
||||||
# specific values (like default content type selection).
|
|
||||||
if isinstance(rv, (text_type, bytes, bytearray)):
|
if isinstance(rv, (text_type, bytes, bytearray)):
|
||||||
rv = self.response_class(rv, headers=headers,
|
# let the response class set the status and headers instead of
|
||||||
status=status_or_headers)
|
# waiting to do it manually, so that the class can handle any
|
||||||
headers = status_or_headers = None
|
# special logic
|
||||||
|
rv = self.response_class(rv, status=status, headers=headers)
|
||||||
|
status = headers = None
|
||||||
else:
|
else:
|
||||||
|
# evaluate a WSGI callable, or coerce a different response
|
||||||
|
# class to the correct type
|
||||||
|
try:
|
||||||
rv = self.response_class.force_type(rv, request.environ)
|
rv = self.response_class.force_type(rv, request.environ)
|
||||||
|
except TypeError as e:
|
||||||
|
new_error = TypeError(
|
||||||
|
'{e}\nThe view function did not return a valid'
|
||||||
|
' response. The return type must be a string, tuple,'
|
||||||
|
' Response instance, or WSGI callable, but it was a'
|
||||||
|
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
|
||||||
|
)
|
||||||
|
reraise(TypeError, new_error, sys.exc_info()[2])
|
||||||
|
|
||||||
if status_or_headers is not None:
|
# prefer the status if it was provided
|
||||||
if isinstance(status_or_headers, string_types):
|
if status is not None:
|
||||||
rv.status = status_or_headers
|
if isinstance(status, (text_type, bytes, bytearray)):
|
||||||
|
rv.status = status
|
||||||
else:
|
else:
|
||||||
rv.status_code = status_or_headers
|
rv.status_code = status
|
||||||
|
|
||||||
|
# extend existing headers with provided headers
|
||||||
if headers:
|
if headers:
|
||||||
rv.headers.extend(headers)
|
rv.headers.extend(headers)
|
||||||
|
|
||||||
|
@ -1759,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):
|
||||||
|
@ -1797,16 +1929,16 @@ class Flask(_PackageBoundObject):
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def preprocess_request(self):
|
def preprocess_request(self):
|
||||||
"""Called before the actual request dispatching and will
|
"""Called before the request is dispatched. Calls
|
||||||
call each :meth:`before_request` decorated function, passing no
|
:attr:`url_value_preprocessors` registered with the app and the
|
||||||
arguments.
|
current blueprint (if any). Then calls :attr:`before_request_funcs`
|
||||||
If any of these functions returns a value, it's handled as
|
registered with the app and the blueprint.
|
||||||
if it was the return value from the view and further
|
|
||||||
request handling is stopped.
|
|
||||||
|
|
||||||
This also triggers the :meth:`url_value_preprocessor` functions before
|
If any :meth:`before_request` handler returns a non-None value, the
|
||||||
the actual :meth:`before_request` functions are called.
|
value is handled as if it was the return value from the view, and
|
||||||
|
further request handling is stopped.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bp = _request_ctx_stack.top.request.blueprint
|
bp = _request_ctx_stack.top.request.blueprint
|
||||||
|
|
||||||
funcs = self.url_value_preprocessors.get(None, ())
|
funcs = self.url_value_preprocessors.get(None, ())
|
||||||
|
@ -1846,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):
|
||||||
|
@ -1931,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:
|
||||||
|
@ -1966,14 +2107,17 @@ class Flask(_PackageBoundObject):
|
||||||
exception context to start the response
|
exception context to start the response
|
||||||
"""
|
"""
|
||||||
ctx = self.request_context(environ)
|
ctx = self.request_context(environ)
|
||||||
ctx.push()
|
|
||||||
error = None
|
error = None
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
ctx.push()
|
||||||
response = self.full_dispatch_request()
|
response = self.full_dispatch_request()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = e
|
error = e
|
||||||
response = self.make_response(self.handle_exception(e))
|
response = self.handle_exception(e)
|
||||||
|
except:
|
||||||
|
error = sys.exc_info()[1]
|
||||||
|
raise
|
||||||
return response(environ, start_response)
|
return response(environ, start_response)
|
||||||
finally:
|
finally:
|
||||||
if self.should_ignore_error(error):
|
if self.should_ignore_error(error):
|
||||||
|
|
|
@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject):
|
||||||
warn_on_modifications = False
|
warn_on_modifications = False
|
||||||
_got_registered_once = False
|
_got_registered_once = False
|
||||||
|
|
||||||
|
#: Blueprint local JSON decoder class to use.
|
||||||
|
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
|
||||||
|
json_encoder = None
|
||||||
|
#: Blueprint local JSON decoder class to use.
|
||||||
|
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
|
||||||
|
json_decoder = None
|
||||||
|
|
||||||
def __init__(self, name, import_name, static_folder=None,
|
def __init__(self, name, import_name, static_folder=None,
|
||||||
static_url_path=None, template_folder=None,
|
static_url_path=None, template_folder=None,
|
||||||
url_prefix=None, subdomain=None, url_defaults=None,
|
url_prefix=None, subdomain=None, url_defaults=None,
|
||||||
|
|
222
flask/cli.py
222
flask/cli.py
|
@ -9,43 +9,142 @@
|
||||||
: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
|
||||||
from threading import Lock, Thread
|
import traceback
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
from operator import attrgetter
|
||||||
|
from threading import Lock, Thread
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from ._compat import iteritems, reraise
|
|
||||||
from .helpers import get_debug_flag
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
from ._compat import iteritems, reraise
|
||||||
|
from .globals import current_app
|
||||||
|
from .helpers import get_debug_flag
|
||||||
|
from ._compat import getargspec
|
||||||
|
|
||||||
|
|
||||||
class NoAppException(click.UsageError):
|
class NoAppException(click.UsageError):
|
||||||
"""Raised if an application cannot be found or loaded."""
|
"""Raised if an application cannot be found or loaded."""
|
||||||
|
|
||||||
|
|
||||||
def find_best_app(module):
|
def find_best_app(script_info, module):
|
||||||
"""Given a module instance this tries to find the best possible
|
"""Given a module instance this tries to find the best possible
|
||||||
application in the module or raises an exception.
|
application in the module or raises an exception.
|
||||||
"""
|
"""
|
||||||
from . import Flask
|
from . import Flask
|
||||||
|
|
||||||
# Search for the most common names first.
|
# Search for the most common names first.
|
||||||
for attr_name in 'app', 'application':
|
for attr_name in ('app', 'application'):
|
||||||
app = getattr(module, attr_name, None)
|
app = getattr(module, attr_name, None)
|
||||||
if app is not None and isinstance(app, Flask):
|
if isinstance(app, Flask):
|
||||||
return app
|
return app
|
||||||
|
|
||||||
# Otherwise find the only object that is a Flask instance.
|
# Otherwise find the only object that is a Flask instance.
|
||||||
matches = [v for k, v in iteritems(module.__dict__)
|
matches = [
|
||||||
if isinstance(v, Flask)]
|
v for k, v in iteritems(module.__dict__) if isinstance(v, Flask)
|
||||||
|
]
|
||||||
|
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
raise NoAppException('Failed to find application in module "%s". Are '
|
elif len(matches) > 1:
|
||||||
'you sure it contains a Flask application? Maybe '
|
raise NoAppException(
|
||||||
'you wrapped it in a WSGI middleware or you are '
|
'Auto-detected multiple Flask applications in module "{module}".'
|
||||||
'using a factory function.' % module.__name__)
|
' Use "FLASK_APP={module}:name" to specify the correct'
|
||||||
|
' one.'.format(module=module.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Search for app factory functions.
|
||||||
|
for attr_name in ('create_app', 'make_app'):
|
||||||
|
app_factory = getattr(module, attr_name, None)
|
||||||
|
|
||||||
|
if inspect.isfunction(app_factory):
|
||||||
|
try:
|
||||||
|
app = call_factory(app_factory, script_info)
|
||||||
|
if isinstance(app, Flask):
|
||||||
|
return app
|
||||||
|
except TypeError:
|
||||||
|
raise NoAppException(
|
||||||
|
'Auto-detected "{function}()" in module "{module}", but '
|
||||||
|
'could not call it without specifying arguments.'.format(
|
||||||
|
function=attr_name, module=module.__name__
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
raise NoAppException(
|
||||||
|
'Failed to find application in module "{module}". Are you sure '
|
||||||
|
'it contains a Flask application? Maybe you wrapped it in a WSGI '
|
||||||
|
'middleware.'.format(module=module.__name__)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def call_factory(app_factory, script_info, arguments=()):
|
||||||
|
"""Takes an app factory, a ``script_info` object and optionally a tuple
|
||||||
|
of arguments. Checks for the existence of a script_info argument and calls
|
||||||
|
the app_factory depending on that and the arguments provided.
|
||||||
|
"""
|
||||||
|
args_spec = getargspec(app_factory)
|
||||||
|
arg_names = args_spec.args
|
||||||
|
arg_defaults = args_spec.defaults
|
||||||
|
|
||||||
|
if 'script_info' in arg_names:
|
||||||
|
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):
|
||||||
|
@ -77,7 +176,7 @@ def prepare_exec_for_file(filename):
|
||||||
return '.'.join(module[::-1])
|
return '.'.join(module[::-1])
|
||||||
|
|
||||||
|
|
||||||
def locate_app(app_id):
|
def locate_app(script_info, app_id):
|
||||||
"""Attempts to locate the application."""
|
"""Attempts to locate the application."""
|
||||||
__traceback_hide__ = True
|
__traceback_hide__ = True
|
||||||
if ':' in app_id:
|
if ':' in app_id:
|
||||||
|
@ -89,20 +188,23 @@ def locate_app(app_id):
|
||||||
try:
|
try:
|
||||||
__import__(module)
|
__import__(module)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise NoAppException('The file/path provided (%s) does not appear to '
|
# Reraise the ImportError if it occurred within the imported module.
|
||||||
'exist. Please verify the path is correct. If '
|
# Determine this by checking whether the trace has a depth > 1.
|
||||||
'app is not on PYTHONPATH, ensure the extension '
|
if sys.exc_info()[-1].tb_next:
|
||||||
'is .py' % module)
|
stack_trace = traceback.format_exc()
|
||||||
|
raise NoAppException('There was an error trying to import'
|
||||||
|
' the app (%s):\n%s' % (module, stack_trace))
|
||||||
|
else:
|
||||||
|
raise NoAppException('The file/path provided (%s) does not appear'
|
||||||
|
' to exist. Please verify the path is '
|
||||||
|
'correct. If app is not on PYTHONPATH, '
|
||||||
|
'ensure the extension is .py' % module)
|
||||||
|
|
||||||
mod = sys.modules[module]
|
mod = sys.modules[module]
|
||||||
if app_obj is None:
|
if app_obj is None:
|
||||||
app = find_best_app(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():
|
||||||
|
@ -131,9 +233,9 @@ version_option = click.Option(['--version'],
|
||||||
is_flag=True, is_eager=True)
|
is_flag=True, is_eager=True)
|
||||||
|
|
||||||
class DispatchingApp(object):
|
class DispatchingApp(object):
|
||||||
"""Special application that dispatches to a flask application which
|
"""Special application that dispatches to a Flask application which
|
||||||
is imported by name in a background thread. If an error happens
|
is imported by name in a background thread. If an error happens
|
||||||
it is is recorded and shows as part of the WSGI handling which in case
|
it is recorded and shown as part of the WSGI handling which in case
|
||||||
of the Werkzeug debugger means that it shows up in the browser.
|
of the Werkzeug debugger means that it shows up in the browser.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -220,7 +322,7 @@ class ScriptInfo(object):
|
||||||
if self._loaded_app is not None:
|
if self._loaded_app is not None:
|
||||||
return self._loaded_app
|
return self._loaded_app
|
||||||
if self.create_app is not None:
|
if self.create_app is not None:
|
||||||
rv = self.create_app(self)
|
rv = call_factory(self.create_app, self)
|
||||||
else:
|
else:
|
||||||
if not self.app_import_path:
|
if not self.app_import_path:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
|
@ -228,7 +330,7 @@ class ScriptInfo(object):
|
||||||
'the FLASK_APP environment variable.\n\nFor more '
|
'the FLASK_APP environment variable.\n\nFor more '
|
||||||
'information see '
|
'information see '
|
||||||
'http://flask.pocoo.org/docs/latest/quickstart/')
|
'http://flask.pocoo.org/docs/latest/quickstart/')
|
||||||
rv = locate_app(self.app_import_path)
|
rv = locate_app(self, self.app_import_path)
|
||||||
debug = get_debug_flag()
|
debug = get_debug_flag()
|
||||||
if debug is not None:
|
if debug is not None:
|
||||||
rv.debug = debug
|
rv.debug = debug
|
||||||
|
@ -310,6 +412,7 @@ class FlaskGroup(AppGroup):
|
||||||
if add_default_commands:
|
if add_default_commands:
|
||||||
self.add_command(run_command)
|
self.add_command(run_command)
|
||||||
self.add_command(shell_command)
|
self.add_command(shell_command)
|
||||||
|
self.add_command(routes_command)
|
||||||
|
|
||||||
self._loaded_plugin_commands = False
|
self._loaded_plugin_commands = False
|
||||||
|
|
||||||
|
@ -362,7 +465,9 @@ class FlaskGroup(AppGroup):
|
||||||
# want the help page to break if the app does not exist.
|
# want the help page to break if the app does not exist.
|
||||||
# If someone attempts to use the command we try to create
|
# If someone attempts to use the command we try to create
|
||||||
# the app again and this will give us the error.
|
# the app again and this will give us the error.
|
||||||
pass
|
# However, we will not do so silently because that would confuse
|
||||||
|
# users.
|
||||||
|
traceback.print_exc()
|
||||||
return sorted(rv)
|
return sorted(rv)
|
||||||
|
|
||||||
def main(self, *args, **kwargs):
|
def main(self, *args, **kwargs):
|
||||||
|
@ -406,6 +511,13 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
||||||
"""
|
"""
|
||||||
from werkzeug.serving import run_simple
|
from werkzeug.serving import run_simple
|
||||||
|
|
||||||
|
# Set a global flag that indicates that we were invoked from the
|
||||||
|
# command line interface provided server command. This is detected
|
||||||
|
# by Flask.run to make the call into a no-op. This is necessary to
|
||||||
|
# avoid ugly errors when the script that is loaded here also attempts
|
||||||
|
# to start a server.
|
||||||
|
os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1'
|
||||||
|
|
||||||
debug = get_debug_flag()
|
debug = get_debug_flag()
|
||||||
if reload is None:
|
if reload is None:
|
||||||
reload = bool(debug)
|
reload = bool(debug)
|
||||||
|
@ -429,8 +541,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
||||||
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
|
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
|
||||||
|
|
||||||
run_simple(host, port, app, use_reloader=reload,
|
run_simple(host, port, app, use_reloader=reload,
|
||||||
use_debugger=debugger, threaded=with_threads,
|
use_debugger=debugger, threaded=with_threads)
|
||||||
passthrough_errors=True)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command('shell', short_help='Runs a shell in the app context.')
|
@click.command('shell', short_help='Runs a shell in the app context.')
|
||||||
|
@ -467,10 +578,57 @@ def shell_command():
|
||||||
code.interact(banner=banner, local=ctx)
|
code.interact(banner=banner, local=ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command('routes', short_help='Show the routes for the app.')
|
||||||
|
@click.option(
|
||||||
|
'--sort', '-s',
|
||||||
|
type=click.Choice(('endpoint', 'methods', 'rule', 'match')),
|
||||||
|
default='endpoint',
|
||||||
|
help=(
|
||||||
|
'Method to sort routes by. "match" is the order that Flask will match '
|
||||||
|
'routes when dispatching a request.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--all-methods',
|
||||||
|
is_flag=True,
|
||||||
|
help="Show HEAD and OPTIONS methods."
|
||||||
|
)
|
||||||
|
@with_appcontext
|
||||||
|
def routes_command(sort, all_methods):
|
||||||
|
"""Show all registered routes with endpoints and methods."""
|
||||||
|
|
||||||
|
rules = list(current_app.url_map.iter_rules())
|
||||||
|
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
|
||||||
|
|
||||||
|
if sort in ('endpoint', 'rule'):
|
||||||
|
rules = sorted(rules, key=attrgetter(sort))
|
||||||
|
elif sort == 'methods':
|
||||||
|
rules = sorted(rules, key=lambda rule: sorted(rule.methods))
|
||||||
|
|
||||||
|
rule_methods = [
|
||||||
|
', '.join(sorted(rule.methods - ignored_methods)) for rule in rules
|
||||||
|
]
|
||||||
|
|
||||||
|
headers = ('Endpoint', 'Methods', 'Rule')
|
||||||
|
widths = (
|
||||||
|
max(len(rule.endpoint) for rule in rules),
|
||||||
|
max(len(methods) for methods in rule_methods),
|
||||||
|
max(len(rule.rule) for rule in rules),
|
||||||
|
)
|
||||||
|
widths = [max(len(h), w) for h, w in zip(headers, widths)]
|
||||||
|
row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths)
|
||||||
|
|
||||||
|
click.echo(row.format(*headers).strip())
|
||||||
|
click.echo(row.format(*('-' * width for width in widths)))
|
||||||
|
|
||||||
|
for rule, methods in zip(rules, rule_methods):
|
||||||
|
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
|
||||||
|
|
||||||
|
|
||||||
cli = FlaskGroup(help="""\
|
cli = FlaskGroup(help="""\
|
||||||
This shell command acts as general utility script for Flask applications.
|
This shell command acts as general utility script for Flask applications.
|
||||||
|
|
||||||
It loads the application configured (either through the FLASK_APP environment
|
It loads the application configured (through the FLASK_APP environment
|
||||||
variable) and then provides commands either provided by the application or
|
variable) and then provides commands either provided by the application or
|
||||||
Flask itself.
|
Flask itself.
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ class Config(dict):
|
||||||
d = types.ModuleType('config')
|
d = types.ModuleType('config')
|
||||||
d.__file__ = filename
|
d.__file__ = filename
|
||||||
try:
|
try:
|
||||||
with open(filename) as config_file:
|
with open(filename, mode='rb') as config_file:
|
||||||
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue