Merge branch 'master' into reload_templates_run_debug
This commit is contained in:
commit
9e39c506e0
151 changed files with 6351 additions and 3504 deletions
11
.coveragerc
Normal file
11
.coveragerc
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[run]
|
||||
branch = True
|
||||
source =
|
||||
flask
|
||||
tests
|
||||
|
||||
[paths]
|
||||
source =
|
||||
flask
|
||||
.tox/*/lib/python*/site-packages/flask
|
||||
.tox/pypy/site-packages/flask
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
CHANGES merge=union
|
||||
35
.github/ISSUE_TEMPLATE.rst
vendored
35
.github/ISSUE_TEMPLATE.rst
vendored
|
|
@ -1,2 +1,33 @@
|
|||
The issue tracker is a tool to address bugs.
|
||||
Please use the #pocoo IRC channel on freenode or Stack Overflow for questions.
|
||||
**This issue tracker is a tool to address bugs in Flask itself.
|
||||
Please use the #pocoo IRC channel on freenode or Stack Overflow for general
|
||||
questions about using Jinja or issues not related to Jinja.**
|
||||
|
||||
If you'd like to report a bug in Flask, fill out the template below. Provide
|
||||
any any extra information that may be useful / related to your problem.
|
||||
Ideally, create an [MCVE](http://stackoverflow.com/help/mcve), which helps us
|
||||
understand the problem and helps check that it is not caused by something in
|
||||
your code.
|
||||
|
||||
---
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
Tell us what should happen.
|
||||
|
||||
```python
|
||||
Paste a minimal example that causes the problem.
|
||||
```
|
||||
|
||||
### Actual Behavior
|
||||
|
||||
Tell us what happens instead.
|
||||
|
||||
```pytb
|
||||
Paste the full traceback if there was an exception.
|
||||
```
|
||||
|
||||
### Environment
|
||||
|
||||
* Python version:
|
||||
* Flask version:
|
||||
* Werkzeug version:
|
||||
|
|
|
|||
16
.github/PULL_REQUEST_TEMPLATE.rst
vendored
Normal file
16
.github/PULL_REQUEST_TEMPLATE.rst
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Describe what this patch does to fix the issue.
|
||||
|
||||
Link to any relevant issues or pull requests.
|
||||
|
||||
<!--
|
||||
Commit checklist:
|
||||
|
||||
* add tests that fail without the patch
|
||||
* ensure all tests pass with ``pytest``
|
||||
* add documentation to the relevant docstrings or pages
|
||||
* add ``versionadded`` or ``versionchanged`` directives to relevant docstrings
|
||||
* add a changelog entry if this patch changes code
|
||||
|
||||
Tests, coverage, and docs will be run automatically when you submit the pull
|
||||
request, but running them yourself can save time.
|
||||
-->
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -11,3 +11,9 @@ _mailinglist
|
|||
.tox
|
||||
.cache/
|
||||
.idea/
|
||||
|
||||
# Coverage reports
|
||||
htmlcov
|
||||
.coverage
|
||||
.coverage.*
|
||||
*,cover
|
||||
|
|
|
|||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "docs/_themes"]
|
||||
path = docs/_themes
|
||||
url = https://github.com/mitsuhiko/flask-sphinx-themes.git
|
||||
61
.travis.yml
61
.travis.yml
|
|
@ -1,48 +1,37 @@
|
|||
sudo: false
|
||||
language: python
|
||||
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "pypy"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
|
||||
env:
|
||||
- REQUIREMENTS=lowest
|
||||
- REQUIREMENTS=lowest-simplejson
|
||||
- REQUIREMENTS=release
|
||||
- REQUIREMENTS=release-simplejson
|
||||
- REQUIREMENTS=devel
|
||||
- REQUIREMENTS=devel-simplejson
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
# Python 3 support currently does not work with lowest requirements
|
||||
- python: "3.3"
|
||||
env: REQUIREMENTS=lowest
|
||||
- python: "3.3"
|
||||
env: REQUIREMENTS=lowest-simplejson
|
||||
- python: "3.4"
|
||||
env: REQUIREMENTS=lowest
|
||||
- python: "3.4"
|
||||
env: REQUIREMENTS=lowest-simplejson
|
||||
- python: "3.5"
|
||||
env: REQUIREMENTS=lowest
|
||||
- python: "3.5"
|
||||
env: REQUIREMENTS=lowest-simplejson
|
||||
|
||||
include:
|
||||
- python: 3.6
|
||||
env: TOXENV=py,codecov
|
||||
- python: 3.5
|
||||
env: TOXENV=py,codecov
|
||||
- python: 3.4
|
||||
env: TOXENV=py,codecov
|
||||
- python: 3.3
|
||||
env: TOXENV=py,codecov
|
||||
- python: 2.7
|
||||
env: TOXENV=py,codecov
|
||||
- python: 2.6
|
||||
env: TOXENV=py,codecov
|
||||
- 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:
|
||||
- pip install tox
|
||||
- pip install tox
|
||||
|
||||
script:
|
||||
- tox -e py-$REQUIREMENTS
|
||||
- tox
|
||||
|
||||
branches:
|
||||
except:
|
||||
- website
|
||||
cache:
|
||||
- pip
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
|
|
|||
2
AUTHORS
2
AUTHORS
|
|
@ -9,6 +9,7 @@ Development Lead
|
|||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Adam Byrtek
|
||||
- Adam Zapletal
|
||||
- Ali Afshar
|
||||
- Chris Edgemon
|
||||
|
|
@ -21,6 +22,7 @@ Patches and Suggestions
|
|||
- Florent Xicluna
|
||||
- Georg Brandl
|
||||
- Jeff Widman @jeffwidman
|
||||
- Joshua Bronson @jab
|
||||
- Justin Quick
|
||||
- Kenneth Reitz
|
||||
- Keyan Pishdadian
|
||||
|
|
|
|||
142
CHANGES
142
CHANGES
|
|
@ -3,14 +3,148 @@ Flask Changelog
|
|||
|
||||
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`_)
|
||||
- Add ``json`` keyword argument for the test client request methods. This will
|
||||
dump the given object as JSON and set the appropriate content type.
|
||||
(`#2358`_)
|
||||
- Extract JSON handling to a mixin applied to both the request and response
|
||||
classes used by Flask. This adds the ``is_json`` and ``get_json`` methods to
|
||||
the response to make testing JSON response much easier. (`#2358`_)
|
||||
- Removed error handler caching because it caused unexpected results for some
|
||||
exception inheritance hierarchies. Register handlers explicitly for each
|
||||
exception if you don't want to traverse the MRO. (`#2362`_)
|
||||
|
||||
.. _#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
|
||||
.. _#2358: https://github.com/pallets/flask/pull/2358
|
||||
.. _#2362: https://github.com/pallets/flask/pull/2362
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
Released on December 21st 2016, codename Punsch.
|
||||
|
||||
- the cli command now responds to `--version`.
|
||||
- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``.
|
||||
See pull request ``#1849``.
|
||||
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
|
||||
has been removed, as per issue ``#104``. See pull request ``#1849``.
|
||||
- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall back to
|
||||
``application/octet-stream``. See pull request ``#1988``.
|
||||
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
||||
(pull request ``#1730``).
|
||||
- Revert a behavior change that made the dev server crash instead of returning
|
||||
a Internal Server Error (pull request ``#2006``).
|
||||
- Correctly invoke response handlers for both regular request dispatching as
|
||||
well as error handlers.
|
||||
- Disable logger propagation by default for the app logger.
|
||||
- Add support for range requests in ``send_file``.
|
||||
- ``app.test_client`` includes preset default environment, which can now be
|
||||
directly set, instead of per ``client.get``.
|
||||
|
||||
Version 0.11.2
|
||||
--------------
|
||||
|
|
@ -94,6 +228,8 @@ Released on May 29th 2016, codename Absinthe.
|
|||
- Don't leak exception info of already catched exceptions to context teardown
|
||||
handlers (pull request ``#1393``).
|
||||
- Allow custom Jinja environment subclasses (pull request ``#1422``).
|
||||
- Updated extension dev guidelines.
|
||||
|
||||
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
|
||||
- Turn on autoescape for ``flask.templating.render_template_string`` by default
|
||||
(pull request ``#1515``).
|
||||
|
|
@ -325,7 +461,7 @@ Released on September 29th 2011, codename Rakija
|
|||
- Applications now not only have a root path where the resources and modules
|
||||
are located but also an instance path which is the designated place to
|
||||
drop files that are modified at runtime (uploads etc.). Also this is
|
||||
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
|
||||
see :ref:`instance-folders`.
|
||||
- Added the ``APPLICATION_ROOT`` configuration variable.
|
||||
|
|
|
|||
173
CONTRIBUTING.rst
173
CONTRIBUTING.rst
|
|
@ -1,89 +1,166 @@
|
|||
==========================
|
||||
How to contribute to Flask
|
||||
==========================
|
||||
|
||||
Thanks for considering contributing to Flask.
|
||||
Thank you for considering contributing to Flask!
|
||||
|
||||
Support questions
|
||||
=================
|
||||
-----------------
|
||||
|
||||
Please, don't use the issue tracker for this. Check whether the ``#pocoo`` IRC
|
||||
channel on Freenode can help with your issue. If your problem is not strictly
|
||||
Werkzeug or Flask specific, ``#python`` is generally more active.
|
||||
`Stack Overflow <https://stackoverflow.com/>`_ is also worth considering.
|
||||
Please, don't use the issue tracker for this. Use one of the following
|
||||
resources for questions about your own code:
|
||||
|
||||
* The IRC channel ``#pocoo`` on FreeNode.
|
||||
* The IRC channel ``#python`` on FreeNode for more general questions.
|
||||
* The mailing list flask@python.org for long term discussion or larger issues.
|
||||
* Ask on `Stack Overflow`_. Search with Google first using:
|
||||
``site:stackoverflow.com flask {search term, exception message, etc.}``
|
||||
|
||||
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?sort=linked
|
||||
|
||||
Reporting issues
|
||||
================
|
||||
----------------
|
||||
|
||||
- Under which versions of Python does this happen? This is even more important
|
||||
if your issue is encoding related.
|
||||
- Describe what you expected to happen.
|
||||
- If possible, include a `minimal, complete, and verifiable example`_ to help
|
||||
us identify the issue. This also helps check that the issue is not with your
|
||||
own code.
|
||||
- Describe what actually happened. Include the full traceback if there was an
|
||||
exception.
|
||||
- List your Python, Flask, and Werkzeug versions. If possible, check if this
|
||||
issue is already fixed in the repository.
|
||||
|
||||
- Under which versions of Werkzeug does this happen? Check if this issue is
|
||||
fixed in the repository.
|
||||
.. _minimal, complete, and verifiable example: https://stackoverflow.com/help/mcve
|
||||
|
||||
Submitting patches
|
||||
==================
|
||||
------------------
|
||||
|
||||
- Include tests if your patch is supposed to solve a bug, and explain
|
||||
clearly under which circumstances the bug happens. Make sure the test fails
|
||||
without your patch.
|
||||
- Try to follow `PEP8`_, but you may ignore the line length limit if following
|
||||
it would make the code uglier.
|
||||
|
||||
- Try to follow `PEP8 <http://legacy.python.org/dev/peps/pep-0008/>`_, but you
|
||||
may ignore the line-length-limit if following it would make the code uglier.
|
||||
First time setup
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- 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
|
||||
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
||||
- Make sure you have a `GitHub account`_.
|
||||
- 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::
|
||||
git clone https://github.com/{username}/flask
|
||||
cd flask
|
||||
|
||||
pip install pytest
|
||||
- Add the main repository as a remote to update later::
|
||||
|
||||
Clone this repository::
|
||||
git remote add pallets https://github.com/pallets/flask
|
||||
git fetch pallets
|
||||
|
||||
git clone https://github.com/pallets/flask.git
|
||||
- Create a virtualenv::
|
||||
|
||||
Install Flask as an editable package using the current source::
|
||||
python3 -m venv env
|
||||
. env/bin/activate
|
||||
# or "env\Scripts\activate" on Windows
|
||||
|
||||
cd flask
|
||||
pip install --editable .
|
||||
- Install Flask in editable mode with development dependencies::
|
||||
|
||||
Then you can run the testsuite with::
|
||||
pip install -e ".[dev]"
|
||||
|
||||
py.test
|
||||
.. _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
|
||||
|
||||
With only py.test installed, a large part of the testsuite will get skipped
|
||||
though. Whether this is relevant depends on which part of Flask you're working
|
||||
on. Travis is set up to run the full testsuite when you submit your pull
|
||||
request anyways.
|
||||
Start coding
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If you really want to test everything, you will have to install ``tox`` instead
|
||||
of ``pytest``. You can install it with::
|
||||
- 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 🎉
|
||||
|
||||
pip install tox
|
||||
.. _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/
|
||||
|
||||
The ``tox`` command will then run all tests against multiple combinations
|
||||
Python versions and dependency versions.
|
||||
.. _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
|
||||
---------------------
|
||||
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:
|
||||
pip install -r test-requirements.txt -q
|
||||
FLASK_DEBUG= py.test tests examples
|
||||
install-dev:
|
||||
pip install -q -e .[dev]
|
||||
|
||||
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: test-all
|
||||
|
||||
docs: clean-pyc install-dev
|
||||
$(MAKE) -C docs html
|
||||
|
||||
audit:
|
||||
python setup.py audit
|
||||
|
||||
release:
|
||||
python scripts/make-release.py
|
||||
|
||||
ext-test:
|
||||
python tests/flaskext_test.py --browse
|
||||
|
||||
clean-pyc:
|
||||
find . -name '*.pyc' -exec rm -f {} +
|
||||
find . -name '*.pyo' -exec rm -f {} +
|
||||
|
|
@ -39,6 +51,3 @@ ebook:
|
|||
@echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).'
|
||||
@echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.'
|
||||
ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher'
|
||||
|
||||
docs:
|
||||
$(MAKE) -C docs html
|
||||
|
|
|
|||
4
README
4
README
|
|
@ -33,9 +33,9 @@
|
|||
|
||||
Good that you're asking. The tests are in 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
|
||||
|
||||
|
|
|
|||
BIN
docs/_static/pycharm-runconfig.png
vendored
Normal file
BIN
docs/_static/pycharm-runconfig.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
8
docs/_templates/sidebarintro.html
vendored
8
docs/_templates/sidebarintro.html
vendored
|
|
@ -1,6 +1,6 @@
|
|||
<h3>About Flask</h3>
|
||||
<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.
|
||||
</p>
|
||||
<h3>Other Formats</h3>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<h3>Useful Links</h3>
|
||||
<ul>
|
||||
<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="http://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://pypi.python.org/pypi/Flask">Flask @ PyPI</a></li>
|
||||
<li><a href="https://github.com/pallets/flask">Flask @ GitHub</a></li>
|
||||
<li><a href="https://github.com/pallets/flask/issues">Issue Tracker</a></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9
|
||||
85
docs/api.rst
85
docs/api.rst
|
|
@ -30,61 +30,12 @@ Incoming Request Data
|
|||
|
||||
.. autoclass:: Request
|
||||
: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.
|
||||
:inherited-members:
|
||||
|
||||
.. attribute:: environ
|
||||
|
||||
The underlying WSGI environment.
|
||||
|
||||
.. attribute:: method
|
||||
|
||||
The current request method (``POST``, ``GET`` etc.)
|
||||
|
||||
.. attribute:: path
|
||||
.. attribute:: full_path
|
||||
.. attribute:: script_root
|
||||
|
|
@ -114,15 +65,8 @@ Incoming Request Data
|
|||
`url_root` ``u'http://www.example.com/myapplication/'``
|
||||
============= ======================================================
|
||||
|
||||
.. attribute:: is_xhr
|
||||
|
||||
``True`` if the request was triggered via a JavaScript
|
||||
`XMLHttpRequest`. This only works with libraries that support the
|
||||
``X-Requested-With`` header and set it to `XMLHttpRequest`.
|
||||
Libraries that do that are prototype, jQuery and Mochikit and
|
||||
probably some more.
|
||||
|
||||
.. class:: request
|
||||
.. attribute:: request
|
||||
|
||||
To access incoming request data, you can use the global `request`
|
||||
object. Flask parses incoming request data for you and gives you
|
||||
|
|
@ -141,7 +85,7 @@ Response Objects
|
|||
----------------
|
||||
|
||||
.. autoclass:: flask.Response
|
||||
:members: set_cookie, data, mimetype
|
||||
:members: set_cookie, data, mimetype, is_json, get_json
|
||||
|
||||
.. attribute:: headers
|
||||
|
||||
|
|
@ -227,18 +171,6 @@ implementation that Flask is using.
|
|||
.. autoclass:: SessionMixin
|
||||
: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
|
||||
|
||||
The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer
|
||||
|
|
@ -316,13 +248,7 @@ Useful Functions and Classes
|
|||
|
||||
.. autofunction:: url_for
|
||||
|
||||
.. function:: abort(code)
|
||||
|
||||
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:: abort
|
||||
|
||||
.. autofunction:: redirect
|
||||
|
||||
|
|
@ -416,6 +342,8 @@ you are using Flask 0.10 which implies that:
|
|||
.. autoclass:: JSONDecoder
|
||||
:members:
|
||||
|
||||
.. automodule:: flask.json.tag
|
||||
|
||||
Template Rendering
|
||||
------------------
|
||||
|
||||
|
|
@ -711,6 +639,7 @@ The following signals exist in Flask:
|
|||
|
||||
.. _blinker: https://pypi.python.org/pypi/blinker
|
||||
|
||||
.. _class-based-views:
|
||||
|
||||
Class-Based Views
|
||||
-----------------
|
||||
|
|
|
|||
|
|
@ -5,31 +5,37 @@ The Application Context
|
|||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
One of the design ideas behind Flask is that there are two different
|
||||
“states” in which code is executed. The application setup state in which
|
||||
the application implicitly is on the module level. It starts when the
|
||||
:class:`Flask` object is instantiated, and it implicitly ends when the
|
||||
first request comes in. While the application is in this state a few
|
||||
assumptions are true:
|
||||
One of the design ideas behind Flask is that there are at least two
|
||||
different “states” in which code is executed:
|
||||
|
||||
- the programmer can modify the application object safely.
|
||||
- no request handling happened so far
|
||||
- you have to have a reference to the application object in order to
|
||||
modify it, there is no magic proxy that can give you a reference to
|
||||
the application object you're currently creating or modifying.
|
||||
1. The application setup state, in which the application implicitly is
|
||||
on the module level.
|
||||
|
||||
In contrast, during request handling, a couple of other rules exist:
|
||||
This state starts when the :class:`Flask` object is instantiated, and
|
||||
it implicitly ends when the first request comes in. While the
|
||||
application is in this state, a few assumptions are true:
|
||||
|
||||
- while a request is active, the context local objects
|
||||
(:data:`flask.request` and others) point to the current request.
|
||||
- any code can get hold of these objects at any time.
|
||||
- the programmer can modify the application object safely.
|
||||
- no request handling happened so far
|
||||
- you have to have a reference to the application object in order to
|
||||
modify it, there is no magic proxy that can give you a reference to
|
||||
the application object you're currently creating or modifying.
|
||||
|
||||
There is a third state which is sitting in between a little bit.
|
||||
Sometimes you are dealing with an application in a way that is similar to
|
||||
how you interact with applications during request handling; just that there
|
||||
is no request active. Consider, for instance, that you're sitting in an
|
||||
interactive Python shell and interacting with the application, or a
|
||||
command line application.
|
||||
2. In contrast, in the request handling state, a couple of other rules
|
||||
exist:
|
||||
|
||||
- while a request is active, the context local objects
|
||||
(:data:`flask.request` and others) point to the current request.
|
||||
- any code can get hold of these objects at any time.
|
||||
|
||||
3. There is also a third state somewhere in between 'module-level' and
|
||||
'request-handling':
|
||||
|
||||
Sometimes you are dealing with an application in a way that is similar to
|
||||
how you interact with applications during request handling, but without
|
||||
there being an active request. Consider, for instance, that you're
|
||||
sitting in an interactive Python shell and interacting with the
|
||||
application, or a command line application.
|
||||
|
||||
The application context is what powers the :data:`~flask.current_app`
|
||||
context local.
|
||||
|
|
@ -74,9 +80,9 @@ The application context is also used by the :func:`~flask.url_for`
|
|||
function in case a ``SERVER_NAME`` was configured. This allows you to
|
||||
generate URLs even in the absence of a request.
|
||||
|
||||
If no request context has been pushed and an application context has
|
||||
not been explicitly set, a ``RuntimeError`` will be raised.
|
||||
::
|
||||
If no request context has been pushed and an application context has
|
||||
not been explicitly set, a ``RuntimeError`` will be raised. ::
|
||||
|
||||
RuntimeError: Working outside of application context.
|
||||
|
||||
Locality of the Context
|
||||
|
|
|
|||
|
|
@ -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
|
||||
developed, it became useful to a wide audience. As you grow your codebase,
|
||||
don't just use Flask -- understand it. Read the source. Flask's code is
|
||||
written to be read; 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
|
||||
internal utilities so that you can find the hook points needed for your
|
||||
project.
|
||||
|
|
|
|||
|
|
@ -177,11 +177,11 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
|
|||
admin = Blueprint('admin', __name__, template_folder='templates')
|
||||
|
||||
For static files, the path can be absolute or relative to the blueprint
|
||||
resource folder.
|
||||
resource folder.
|
||||
|
||||
The template folder is added to the search path of templates but with a lower
|
||||
priority than the actual application's template folder. That way you can
|
||||
easily override templates that a blueprint provides in the actual application.
|
||||
The template folder is added to the search path of templates but with a lower
|
||||
priority than the actual application's template folder. That way you can
|
||||
easily override templates that a blueprint provides in the actual application.
|
||||
This also means that if you don't want a blueprint template to be accidentally
|
||||
overridden, make sure that no other blueprint or actual application template
|
||||
has the same relative path. When multiple blueprints provide the same relative
|
||||
|
|
@ -194,7 +194,7 @@ want to render the template ``'admin/index.html'`` and you have provided
|
|||
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
|
||||
for the extra ``admin`` folder is to avoid getting our template overridden
|
||||
by a template named ``index.html`` in the actual application template
|
||||
folder.
|
||||
folder.
|
||||
|
||||
To further reiterate this: if you have a blueprint named ``admin`` and you
|
||||
want to render a template called :file:`index.html` which is specific to this
|
||||
|
|
@ -245,4 +245,22 @@ Here is an example for a "404 Page Not Found" exception::
|
|||
def page_not_found(e):
|
||||
return render_template('pages/404.html')
|
||||
|
||||
Most errorhandlers will simply work as expected; however, there is a caveat
|
||||
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||
access. This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint 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`.
|
||||
|
|
|
|||
75
docs/cli.rst
75
docs/cli.rst
|
|
@ -32,7 +32,7 @@ Python module that contains a Flask application.
|
|||
|
||||
In that imported file the name of the app needs to be called ``app`` or
|
||||
optionally be specified after a colon. For instance
|
||||
`mymodule:application` would tell it to use the `application` object in
|
||||
``mymodule:application`` would tell it to use the `application` object in
|
||||
the :file:`mymodule.py` file.
|
||||
|
||||
Given a :file:`hello.py` file with the application in it named ``app``
|
||||
|
|
@ -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
|
||||
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
|
||||
----------
|
||||
|
||||
The :command:`flask` script can also be instructed to enable the debug
|
||||
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
||||
set to ``1`` debug is enabled or ``0`` disables it.
|
||||
|
||||
Or with a filename::
|
||||
set to ``1`` debug is enabled or ``0`` disables it::
|
||||
|
||||
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
|
||||
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
||||
|
||||
Once this has happened you can make the flask command automatically pick
|
||||
it up::
|
||||
Once this has happened you can make the :command:`flask` command automatically
|
||||
pick it up::
|
||||
|
||||
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
||||
export FLASK_APP=/path/to/autoapp.py
|
||||
|
|
@ -218,13 +228,13 @@ step.
|
|||
CLI Plugins
|
||||
-----------
|
||||
|
||||
Flask extensions can always patch the `Flask.cli` instance with more
|
||||
Flask extensions can always patch the :attr:`Flask.cli` instance with more
|
||||
commands if they want. However there is a second way to add CLI plugins
|
||||
to Flask which is through `setuptools`. If you make a Python package that
|
||||
should export a Flask command line plugin you can ship a `setup.py` file
|
||||
to Flask which is through ``setuptools``. If you make a Python package that
|
||||
should export a Flask command line plugin you can ship a :file:`setup.py` file
|
||||
that declares an entrypoint that points to a click command:
|
||||
|
||||
Example `setup.py`::
|
||||
Example :file:`setup.py`::
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
|
|
@ -237,7 +247,7 @@ Example `setup.py`::
|
|||
''',
|
||||
)
|
||||
|
||||
Inside `mypackage/commands.py` you can then export a Click object::
|
||||
Inside :file:`mypackage/commands.py` you can then export a Click object::
|
||||
|
||||
import click
|
||||
|
||||
|
|
@ -248,3 +258,46 @@ Inside `mypackage/commands.py` you can then export a Click object::
|
|||
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
|
||||
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``.
|
||||
|
|
|
|||
98
docs/conf.py
98
docs/conf.py
|
|
@ -11,13 +11,18 @@
|
|||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
from __future__ import print_function
|
||||
import sys, os
|
||||
import os
|
||||
import sys
|
||||
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,
|
||||
# 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.
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
sys.path.append(os.path.abspath('.'))
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
|
|
@ -32,6 +37,14 @@ extensions = [
|
|||
'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.
|
||||
templates_path = ['_templates']
|
||||
|
||||
|
|
@ -46,22 +59,21 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'Flask'
|
||||
copyright = u'2015, Armin Ronacher'
|
||||
copyright = u'2010 - {0}, Armin Ronacher'.format(BUILD_DATE.year)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
import pkg_resources
|
||||
try:
|
||||
release = pkg_resources.get_distribution('Flask').version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
print('Flask must be installed to build the documentation.')
|
||||
print('Install from source using `pip install -e .` in a virtualenv.')
|
||||
sys.exit(1)
|
||||
del pkg_resources
|
||||
|
||||
if 'dev' in release:
|
||||
release = release.split('dev')[0] + 'dev'
|
||||
release = ''.join(release.partition('dev')[:2])
|
||||
|
||||
version = '.'.join(release.split('.')[:2])
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
|
|
@ -100,17 +112,15 @@ exclude_patterns = ['_build']
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'flask'
|
||||
# html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {
|
||||
'touch_icon': 'touch-icon.png'
|
||||
}
|
||||
# html_theme_options = {}
|
||||
|
||||
# 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
|
||||
# "<project> v<release> documentation".
|
||||
|
|
@ -126,7 +136,7 @@ html_theme_path = ['_themes']
|
|||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = "flask-favicon.ico"
|
||||
html_favicon = '_static/flask-favicon.ico'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
|
|
@ -143,9 +153,18 @@ html_static_path = ['_static']
|
|||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
|
||||
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
|
||||
'sourcelink.html', 'searchbox.html']
|
||||
'index': [
|
||||
'sidebarintro.html',
|
||||
'sourcelink.html',
|
||||
'searchbox.html'
|
||||
],
|
||||
'**': [
|
||||
'sidebarlogo.html',
|
||||
'localtoc.html',
|
||||
'relations.html',
|
||||
'sourcelink.html',
|
||||
'searchbox.html'
|
||||
]
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
|
|
@ -187,8 +206,7 @@ htmlhelp_basename = 'Flaskdoc'
|
|||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('latexindex', 'Flask.tex', u'Flask Documentation',
|
||||
u'Armin Ronacher', 'manual'),
|
||||
('latexindex', 'Flask.tex', u'Flask Documentation', u'Armin Ronacher', 'manual'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
|
@ -198,10 +216,10 @@ latex_documents = [
|
|||
latex_use_modindex = False
|
||||
|
||||
latex_elements = {
|
||||
'fontpkg': r'\usepackage{mathpazo}',
|
||||
'papersize': 'a4paper',
|
||||
'pointsize': '12pt',
|
||||
'preamble': r'\usepackage{flaskstyle}'
|
||||
'fontpkg': r'\usepackage{mathpazo}',
|
||||
'papersize': 'a4paper',
|
||||
'pointsize': '12pt',
|
||||
'preamble': r'\usepackage{flaskstyle}'
|
||||
}
|
||||
latex_use_parts = True
|
||||
|
||||
|
|
@ -223,7 +241,7 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
|
|||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
#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.
|
||||
#epub_identifier = ''
|
||||
|
||||
|
|
@ -245,32 +263,18 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
|
|||
#epub_tocdepth = 3
|
||||
|
||||
intersphinx_mapping = {
|
||||
'https://docs.python.org/dev': None,
|
||||
'http://werkzeug.pocoo.org/docs/': None,
|
||||
'http://click.pocoo.org/': None,
|
||||
'http://jinja.pocoo.org/docs/': None,
|
||||
'http://www.sqlalchemy.org/docs/': None,
|
||||
'https://wtforms.readthedocs.io/en/latest/': None,
|
||||
'https://pythonhosted.org/blinker/': None
|
||||
'python': ('https://docs.python.org/3/', None),
|
||||
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
||||
'click': ('http://click.pocoo.org/', None),
|
||||
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
||||
'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None),
|
||||
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
||||
'blinker': ('https://pythonhosted.org/blinker/', None)
|
||||
}
|
||||
|
||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||
|
||||
# fall back if theme is not there
|
||||
try:
|
||||
__import__('flask_theme_support')
|
||||
except ImportError as e:
|
||||
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)
|
||||
|
||||
pygments_style = 'tango'
|
||||
html_theme = 'default'
|
||||
html_theme_options = {}
|
||||
|
||||
html_theme_options = {
|
||||
'touch_icon': 'touch-icon.png'
|
||||
}
|
||||
|
||||
# unwrap decorators
|
||||
def unwrap_decorators():
|
||||
|
|
|
|||
462
docs/config.rst
462
docs/config.rst
|
|
@ -3,8 +3,6 @@
|
|||
Configuration Handling
|
||||
======================
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
Applications need some kind of configuration. There are different settings
|
||||
you might want to change depending on the application environment like
|
||||
toggling the debug mode, setting the secret key, and other such
|
||||
|
|
@ -44,178 +42,263 @@ method::
|
|||
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
|
||||
----------------------------
|
||||
|
||||
The following configuration values are used internally by Flask:
|
||||
|
||||
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
|
||||
.. py:data:: DEBUG
|
||||
|
||||
================================= =========================================
|
||||
``DEBUG`` enable/disable debug mode
|
||||
``TESTING`` enable/disable testing mode
|
||||
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
|
||||
propagation of exceptions. If not set or
|
||||
explicitly set to ``None`` this is
|
||||
implicitly true if either ``TESTING`` or
|
||||
``DEBUG`` is true.
|
||||
``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in
|
||||
debug mode the request context is not
|
||||
popped on exceptions to enable debuggers
|
||||
to introspect the data. This can be
|
||||
disabled by this key. You can also use
|
||||
this setting to force-enable it for non
|
||||
debug execution which might be useful to
|
||||
debug production applications (but also
|
||||
very risky).
|
||||
``SECRET_KEY`` the secret key
|
||||
``SESSION_COOKIE_NAME`` the name of the session cookie
|
||||
``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If
|
||||
this is not set, the cookie will be
|
||||
valid for all subdomains of
|
||||
``SERVER_NAME``.
|
||||
``SESSION_COOKIE_PATH`` the path for the session cookie. If
|
||||
this is not set the cookie will be valid
|
||||
for all of ``APPLICATION_ROOT`` or if
|
||||
that is not set for ``'/'``.
|
||||
``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set
|
||||
with the httponly flag. Defaults to
|
||||
``True``.
|
||||
``SESSION_COOKIE_SECURE`` controls if the cookie should be set
|
||||
with the secure flag. Defaults to
|
||||
``False``.
|
||||
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
||||
:class:`datetime.timedelta` object.
|
||||
Starting with Flask 0.8 this can also be
|
||||
an integer representing seconds.
|
||||
``SESSION_REFRESH_EACH_REQUEST`` this flag controls how permanent
|
||||
sessions are refreshed. If set to ``True``
|
||||
(which is the default) then the cookie
|
||||
is refreshed each request which
|
||||
automatically bumps the lifetime. If
|
||||
set to ``False`` a `set-cookie` header is
|
||||
only sent if the session is modified.
|
||||
Non permanent sessions are not affected
|
||||
by this.
|
||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
||||
``LOGGER_NAME`` the name of the logger
|
||||
``LOGGER_HANDLER_POLICY`` the policy of the default logging
|
||||
handler. The default is ``'always'``
|
||||
which means that the default logging
|
||||
handler is always active. ``'debug'``
|
||||
will only activate logging in debug
|
||||
mode, ``'production'`` will only log in
|
||||
production and ``'never'`` disables it
|
||||
entirely.
|
||||
``SERVER_NAME`` the name and port number of the server.
|
||||
Required for subdomain support (e.g.:
|
||||
``'myapp.dev:5000'``) Note that
|
||||
localhost does not support subdomains so
|
||||
setting this to “localhost” does not
|
||||
help. Setting a ``SERVER_NAME`` also
|
||||
by default enables URL generation
|
||||
without a request context but with an
|
||||
application context.
|
||||
``APPLICATION_ROOT`` 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.
|
||||
================================= =========================================
|
||||
Enable debug mode. When using the development server with ``flask run`` or
|
||||
``app.run``, an interactive debugger will be shown for unhanlded
|
||||
exceptions, and the server will be reloaded when code changes.
|
||||
|
||||
.. admonition:: More on ``SERVER_NAME``
|
||||
**Do not enable debug mode in production.**
|
||||
|
||||
The ``SERVER_NAME`` key is used for the subdomain support. Because
|
||||
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.
|
||||
Default: ``False``
|
||||
|
||||
Please keep in mind that not only Flask has the problem of not knowing
|
||||
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`_.
|
||||
.. py:data:: TESTING
|
||||
|
||||
.. _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
|
||||
``LOGGER_NAME``
|
||||
|
|
@ -262,7 +345,7 @@ So a common pattern is this::
|
|||
|
||||
This first loads the configuration from the
|
||||
`yourapplication.default_settings` module and then overrides the values
|
||||
with the contents of the file the :envvar:``YOURAPPLICATION_SETTINGS``
|
||||
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||
environment variable points to. This environment variable can be set on
|
||||
Linux or OS X with the export command in the shell before starting the
|
||||
server::
|
||||
|
|
@ -292,6 +375,54 @@ methods on the config object as well to load from individual files. For a
|
|||
complete reference, read the :class:`~flask.Config` object's
|
||||
documentation.
|
||||
|
||||
Configuring from Environment Variables
|
||||
--------------------------------------
|
||||
|
||||
In addition to pointing to configuration files using environment variables, you
|
||||
may find it useful (or necessary) to control your configuration values directly
|
||||
from the environment.
|
||||
|
||||
Environment variables can be set on Linux or OS X with the export command in
|
||||
the shell before starting the server::
|
||||
|
||||
$ export SECRET_KEY='?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
|
||||
$ export DEBUG=False
|
||||
$ python run-app.py
|
||||
* Running on http://127.0.0.1:5000/
|
||||
* Restarting with reloader...
|
||||
|
||||
On Windows systems use the `set` builtin instead::
|
||||
|
||||
>set SECRET_KEY='?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
|
||||
>set DEBUG=False
|
||||
|
||||
While this approach is straightforward to use, it is important to remember that
|
||||
environment variables are strings -- they are not automatically deserialized
|
||||
into Python types.
|
||||
|
||||
Here is an example of a configuration file that uses environment variables::
|
||||
|
||||
# Example configuration
|
||||
import os
|
||||
|
||||
ENVIRONMENT_DEBUG = os.environ.get("DEBUG", default=False)
|
||||
if ENVIRONMENT_DEBUG.lower() in ("f", "false"):
|
||||
ENVIRONMENT_DEBUG = False
|
||||
|
||||
DEBUG = ENVIRONMENT_DEBUG
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", default=None)
|
||||
if not SECRET_KEY:
|
||||
raise ValueError("No secret key set for Flask application")
|
||||
|
||||
|
||||
Notice that any value besides an empty string will be interpreted as a boolean
|
||||
``True`` value in Python, which requires care if an environment explicitly sets
|
||||
values intended to be ``False``.
|
||||
|
||||
Make sure to load the configuration very early on, so that extensions have the
|
||||
ability to access the configuration when starting up. There are other methods
|
||||
on the config object as well to load from individual files. For a complete
|
||||
reference, read the :class:`~flask.Config` class documentation.
|
||||
|
||||
Configuration Best Practices
|
||||
----------------------------
|
||||
|
|
@ -463,3 +594,4 @@ Example usage for both::
|
|||
# or via open_instance_resource:
|
||||
with app.open_instance_resource('application.cfg') as f:
|
||||
config = f.read()
|
||||
|
||||
|
|
|
|||
|
|
@ -59,3 +59,4 @@ Design notes, legal information and changelog are here for the interested.
|
|||
upgrading
|
||||
changelog
|
||||
license
|
||||
contributing
|
||||
|
|
|
|||
1
docs/contributing.rst
Normal file
1
docs/contributing.rst
Normal file
|
|
@ -0,0 +1 @@
|
|||
.. include:: ../CONTRIBUTING.rst
|
||||
|
|
@ -144,7 +144,7 @@ A basic FastCGI configuration for lighttpd looks like that::
|
|||
)
|
||||
|
||||
alias.url = (
|
||||
"/static/" => "/path/to/your/static"
|
||||
"/static/" => "/path/to/your/static/"
|
||||
)
|
||||
|
||||
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
|
||||
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).
|
||||
|
||||
Configuring nginx
|
||||
|
|
@ -234,7 +234,7 @@ python path. Common problems are:
|
|||
web server.
|
||||
- Different python interpreters being used.
|
||||
|
||||
.. _nginx: http://nginx.org/
|
||||
.. _lighttpd: http://www.lighttpd.net/
|
||||
.. _nginx: https://nginx.org/
|
||||
.. _lighttpd: https://www.lighttpd.net/
|
||||
.. _cherokee: http://cherokee-project.com/
|
||||
.. _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 Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
||||
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
||||
- `Deploying Flask on AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
||||
- `Deploying on Azure (IIS) <https://azure.microsoft.com/documentation/articles/web-sites-python-configure/>`_
|
||||
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||
|
||||
Self-hosted options
|
||||
-------------------
|
||||
|
|
@ -30,8 +32,8 @@ Self-hosted options
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
mod_wsgi
|
||||
wsgi-standalone
|
||||
uwsgi
|
||||
mod_wsgi
|
||||
fastcgi
|
||||
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
|
||||
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`
|
||||
---------------------
|
||||
|
|
@ -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`_.
|
||||
|
||||
.. _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
|
||||
|
||||
|
|
@ -130,12 +130,12 @@ to httpd 2.4 syntax
|
|||
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/
|
||||
.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide
|
||||
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
||||
.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html
|
||||
.. _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
|
||||
---------------
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Given a flask application in myapp.py, use the following command:
|
|||
|
||||
.. sourcecode:: text
|
||||
|
||||
$ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app
|
||||
$ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app
|
||||
|
||||
The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi,
|
||||
since its smarter about that. It is used together with the ``--mount`` directive
|
||||
|
|
@ -66,7 +66,7 @@ to have it in the URL root its a bit simpler::
|
|||
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||
}
|
||||
|
||||
.. _nginx: http://nginx.org/
|
||||
.. _lighttpd: http://www.lighttpd.net/
|
||||
.. _nginx: https://nginx.org/
|
||||
.. _lighttpd: https://www.lighttpd.net/
|
||||
.. _cherokee: http://cherokee-project.com/
|
||||
.. _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/
|
||||
.. _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
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Error Logging Tools
|
|||
Sending error mails, even if just for critical ones, can become
|
||||
overwhelming if enough users are hitting the error and log files are
|
||||
typically never looked at. This is why we recommend using `Sentry
|
||||
<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
|
||||
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
||||
<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
|
||||
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
|
||||
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.
|
||||
This can be done by registering error handlers.
|
||||
|
||||
Error handlers are normal :ref:`views` but instead of being registered for
|
||||
routes they are registered for exceptions that are rised while trying to
|
||||
do something else.
|
||||
An error handler is a normal view function that return a response, but instead
|
||||
of being registered for a route, it is registered for an exception or HTTP
|
||||
status code that would is raised while trying to handle a request.
|
||||
|
||||
Registering
|
||||
```````````
|
||||
|
||||
Register error handlers using :meth:`~flask.Flask.errorhandler` or
|
||||
:meth:`~flask.Flask.register_error_handler`::
|
||||
Register handlers by decorating a function with
|
||||
:meth:`~flask.Flask.errorhandler`. Or use
|
||||
:meth:`~flask.Flask.register_error_handler` to register the function later.
|
||||
Remember to set the error code when returning the response. ::
|
||||
|
||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||
def handle_bad_request(e):
|
||||
return 'bad request!'
|
||||
|
||||
app.register_error_handler(400, lambda e: 'bad request!')
|
||||
return 'bad request!', 400
|
||||
|
||||
# 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.BadRequest` from the example and their HTTP codes
|
||||
are interchangeable when handed to the registration methods or decorator
|
||||
(``BadRequest.code == 400``).
|
||||
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
|
||||
when registering handlers. (``BadRequest.code == 400``)
|
||||
|
||||
You are however not limited to :exc:`~werkzeug.exceptions.HTTPException`
|
||||
or HTTP status codes but can register a handler for every exception class you
|
||||
like.
|
||||
Non-standard HTTP codes cannot be registered by code because they are not known
|
||||
by Werkzeug. Instead, define a subclass of
|
||||
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
|
||||
register and raise that exception class. ::
|
||||
|
||||
.. 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
|
||||
they are registered for instead of the order they are registered in.
|
||||
app.register_error_handler(InsuffcientStorage, handle_507)
|
||||
|
||||
raise InsufficientStorage()
|
||||
|
||||
Handlers can be registered for any exception class, not just
|
||||
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
|
||||
codes. Handlers can be registered for a specific class, or for all subclasses
|
||||
of a parent class.
|
||||
|
||||
Handling
|
||||
````````
|
||||
|
||||
Once an exception instance is raised, its class hierarchy is traversed,
|
||||
and searched for in the exception classes for which handlers are registered.
|
||||
The most specific handler is selected.
|
||||
When an exception is caught by Flask while handling a request, it is first
|
||||
looked up by code. If no handler is registered for the code, it is looked up
|
||||
by its class hierarchy; the most specific handler is chosen. If no handler is
|
||||
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
|
||||
generic message about their code, while other exceptions are converted to a
|
||||
generic 500 Internal Server Error.
|
||||
|
||||
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`,
|
||||
the more specific :exc:`ConnectionRefusedError` handler is called on the
|
||||
exception instance, and its response is shown to the user.
|
||||
the more specific :exc:`ConnectionRefusedError` handler is called with the
|
||||
exception instance to generate the response.
|
||||
|
||||
Handlers registered on the blueprint take precedence over those registered
|
||||
globally on the application, assuming a blueprint is handling the request that
|
||||
raises the exception. However, the blueprint cannot handle 404 routing errors
|
||||
because the 404 occurs at the routing level before the blueprint can be
|
||||
determined.
|
||||
|
||||
.. 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
|
||||
-----------
|
||||
|
|
@ -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
|
||||
do that in the log formatter format string.
|
||||
|
||||
Here some example setups:
|
||||
Here are some example setups:
|
||||
|
||||
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 |
|
||||
| | issued (if available). |
|
||||
+------------------+----------------------------------------------------+
|
||||
| ``%(asctime)s`` | Human-readable time when the LogRecord` was |
|
||||
| | created. By default this is of the form |
|
||||
| ``%(asctime)s`` | Human-readable time when the |
|
||||
| | :class:`~logging.LogRecord` was created. |
|
||||
| | By default this is of the form |
|
||||
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
||||
| | the comma are millisecond portion of the time). |
|
||||
| | 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
|
||||
their :file:`setup.py` files.
|
||||
|
||||
Flask sets up a redirect package called :data:`flask.ext` where users
|
||||
should import the extensions from. If you for instance have a package
|
||||
called ``flask_something`` users would import it as
|
||||
``flask.ext.something``. This is done to transition from the old
|
||||
namespace packages. See :ref:`ext-import-transition` for more details.
|
||||
|
||||
But what do extensions look like themselves? An extension has to ensure
|
||||
that it works with multiple Flask application instances at once. This is
|
||||
a requirement because many people will use patterns like the
|
||||
|
|
@ -48,7 +42,7 @@ that people can easily install the development version into their
|
|||
virtualenv without having to download the library by hand.
|
||||
|
||||
Flask extensions must be licensed under a BSD, MIT or more liberal license
|
||||
to be able to be enlisted in the Flask Extension Registry. Keep in mind
|
||||
in order to be listed in the Flask Extension Registry. Keep in mind
|
||||
that the Flask Extension Registry is a moderated place and libraries will
|
||||
be reviewed upfront if they behave as required.
|
||||
|
||||
|
|
@ -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
|
||||
object that handles opening and closing database connections.
|
||||
|
||||
What's important about classes is that they encourage to be shared around
|
||||
on module level. In that case, the object itself must not under any
|
||||
When designing your classes, it's important to make them easily reusable
|
||||
at the module level. This means the object itself must not under any
|
||||
circumstances store any application specific state and must be shareable
|
||||
between different application.
|
||||
between different applications.
|
||||
|
||||
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
|
||||
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
|
||||
ideas for nice looking APIs. Especially if you do something nobody before
|
||||
you did, it might be a very good idea to get some more input. This not
|
||||
only to get an idea about what people might want to have from an
|
||||
extension, but also to avoid having multiple developers working on pretty
|
||||
much the same side by side.
|
||||
you did, it might be a very good idea to get some more input. This not only
|
||||
generates useful feedback on what people might want from an extension, but
|
||||
also avoids having multiple developers working in isolation on pretty much the
|
||||
same problem.
|
||||
|
||||
Remember: good API design is hard, so introduce your project on the
|
||||
mailinglist, and let other developers give you a helping hand with
|
||||
|
|
@ -370,10 +364,10 @@ extension to be approved you have to follow these guidelines:
|
|||
3. APIs of approved extensions will be checked for the following
|
||||
characteristics:
|
||||
|
||||
- an approved extension has to support multiple applications
|
||||
running in the same Python process.
|
||||
- it must be possible to use the factory pattern for creating
|
||||
applications.
|
||||
- an approved extension has to support multiple applications
|
||||
running in the same Python process.
|
||||
- it must be possible to use the factory pattern for creating
|
||||
applications.
|
||||
|
||||
4. The license must be BSD/MIT/WTFPL licensed.
|
||||
5. The naming scheme for official extensions is *Flask-ExtensionName* or
|
||||
|
|
@ -387,13 +381,11 @@ extension to be approved you have to follow these guidelines:
|
|||
link to the documentation, website (if there is one) and there
|
||||
must be a link to automatically install the development version
|
||||
(``PackageName==dev``).
|
||||
9. The ``zip_safe`` flag in the setup script must be set to ``False``,
|
||||
even if the extension would be safe for zipping.
|
||||
10. An extension currently has to support Python 2.6 as well as
|
||||
Python 2.7
|
||||
9. The ``zip_safe`` flag in the setup script must be set to ``False``,
|
||||
even if the extension would be safe for zipping.
|
||||
10. An extension currently has to support Python 2.7, Python 3.3 and higher.
|
||||
|
||||
|
||||
.. _ext-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``.
|
||||
|
||||
|
||||
.. _OAuth extension: http://pythonhosted.org/Flask-OAuth/
|
||||
.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/
|
||||
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
||||
.. _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,174 +3,173 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Flask depends on some external libraries, like `Werkzeug
|
||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_.
|
||||
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
||||
applications and a variety of servers for both development and deployment.
|
||||
Jinja2 renders templates.
|
||||
Python Version
|
||||
--------------
|
||||
|
||||
So how do you get all that on your computer quickly? There are many ways you
|
||||
could do that, but the most kick-ass method is virtualenv, so let's have a look
|
||||
at that first.
|
||||
We recommend using the latest version of Python 3. Flask supports Python 3.3
|
||||
and newer, Python 2.6 and newer, and PyPy.
|
||||
|
||||
You will need Python 2.6 or newer to get started, so be sure to have an
|
||||
up-to-date Python 2.x installation. For using Flask with Python 3 have a
|
||||
look at :ref:`python3-support`.
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
.. _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
|
||||
shell access to your production machines, you'll probably want to use it there,
|
||||
too.
|
||||
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||
.. _Jinja: http://jinja.pocoo.org/
|
||||
.. _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,
|
||||
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?
|
||||
Optional dependencies
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Virtualenv to the rescue! Virtualenv enables multiple side-by-side
|
||||
installations of Python, one for each project. It doesn't actually install
|
||||
separate copies of Python, but it does provide a clever way to keep different
|
||||
project environments isolated. Let's see how virtualenv works.
|
||||
These distributions will not be installed automatically. Flask will detect and
|
||||
use them if you install them.
|
||||
|
||||
If you are on Mac OS X or Linux, chances are that one of the following two
|
||||
commands will work for you::
|
||||
* `Blinker`_ provides support for :ref:`signals`.
|
||||
* `SimpleJSON`_ is a fast JSON implementation that is compatible with
|
||||
Python's ``json`` module. It is preferred for JSON operations if it is
|
||||
installed.
|
||||
|
||||
$ sudo easy_install virtualenv
|
||||
.. _Blinker: https://pythonhosted.org/blinker/
|
||||
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
||||
|
||||
or even better::
|
||||
Virtual environments
|
||||
--------------------
|
||||
|
||||
$ sudo pip install virtualenv
|
||||
Use a virtual environment to manage the dependencies for your project, both in
|
||||
development and in production.
|
||||
|
||||
One of these will probably install virtualenv on your system. Maybe it's even
|
||||
in your package manager. If you use Ubuntu, try::
|
||||
What problem does a virtual environment solve? The more Python projects you
|
||||
have, the more likely it is that you need to work with different versions of
|
||||
Python libraries, or even Python itself. Newer versions of libraries for one
|
||||
project can break compatibility in another project.
|
||||
|
||||
$ sudo apt-get install python-virtualenv
|
||||
Virtual environments are independent groups of Python libraries, one for each
|
||||
project. Packages installed for one project will not affect other projects or
|
||||
the operating system's packages.
|
||||
|
||||
If you are on Windows and don't have the :command:`easy_install` command, you must
|
||||
install it first. Check the :ref:`windows-easy-install` section for more
|
||||
information about how to do that. Once you have it installed, run the same
|
||||
commands as above, but without the :command:`sudo` prefix.
|
||||
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
||||
environments. If you're using a modern version of Python, you can continue on
|
||||
to the next section.
|
||||
|
||||
Once you have virtualenv installed, just fire up a shell and create
|
||||
your own environment. I usually create a project folder and a :file:`venv`
|
||||
folder within::
|
||||
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
$ virtualenv venv
|
||||
New python executable in venv/bin/python
|
||||
Installing setuptools, pip............done.
|
||||
.. _install-create-env:
|
||||
|
||||
Now, whenever you want to work on a project, you only have to activate the
|
||||
corresponding environment. On OS X and Linux, do the following::
|
||||
Create an environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
$ . venv/bin/activate
|
||||
Create a project folder and a :file:`venv` folder within:
|
||||
|
||||
If you are a Windows user, the following command is for you::
|
||||
.. code-block:: sh
|
||||
|
||||
$ venv\scripts\activate
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
python3 -m venv venv
|
||||
|
||||
Either way, you should now be using your virtualenv (notice how the prompt of
|
||||
your shell has changed to show the active environment).
|
||||
On Windows:
|
||||
|
||||
And if you want to go back to the real world, use the following command::
|
||||
.. code-block:: bat
|
||||
|
||||
$ deactivate
|
||||
py -3 -m venv venv
|
||||
|
||||
After doing this, the prompt of your shell should be as familiar as before.
|
||||
If you needed to install virtualenv because you are on an older version of
|
||||
Python, use the following command instead:
|
||||
|
||||
Now, let's move on. Enter the following command to get Flask activated in your
|
||||
virtualenv::
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install Flask
|
||||
virtualenv venv
|
||||
|
||||
A few seconds later and you are good to go.
|
||||
On Windows:
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
System-Wide Installation
|
||||
------------------------
|
||||
\Python27\Scripts\virtualenv.exe venv
|
||||
|
||||
This is possible as well, though I do not recommend it. Just run
|
||||
:command:`pip` with root privileges::
|
||||
Activate the environment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
$ sudo pip install Flask
|
||||
Before you work on your project, activate the corresponding environment:
|
||||
|
||||
(On Windows systems, run it in a command-prompt window with administrator
|
||||
privileges, and leave out :command:`sudo`.)
|
||||
.. code-block:: sh
|
||||
|
||||
. venv/bin/activate
|
||||
|
||||
Living on the Edge
|
||||
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
|
||||
can either let :command:`pip` pull in the development version, or you can tell
|
||||
it to operate on a git checkout. Either way, virtualenv is recommended.
|
||||
If you are using Python 2, the venv module is not available. Instead,
|
||||
install `virtualenv`_.
|
||||
|
||||
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
|
||||
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
|
||||
.. code-block:: sh
|
||||
|
||||
This will pull in the dependencies and activate the git head as the current
|
||||
version inside the virtualenv. Then all you have to do is run ``git pull
|
||||
origin`` to update to the latest version.
|
||||
# Debian, Ubuntu
|
||||
sudo apt-get install python-virtualenv
|
||||
|
||||
# CentOS, Fedora
|
||||
sudo yum install python-virtualenv
|
||||
|
||||
.. _windows-easy-install:
|
||||
# Arch
|
||||
sudo pacman -S python-virtualenv
|
||||
|
||||
`pip` and `setuptools` on Windows
|
||||
---------------------------------
|
||||
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
|
||||
|
||||
Sometimes getting the standard "Python packaging tools" like *pip*, *setuptools*
|
||||
and *virtualenv* can be a little trickier, but nothing very hard. The two crucial
|
||||
packages you will need are setuptools and pip - these will let you install
|
||||
anything else (like virtualenv). Fortunately there are two "bootstrap scripts"
|
||||
you can run to install either.
|
||||
.. code-block:: sh
|
||||
|
||||
If you don't currently have either, then `get-pip.py` will install both for you
|
||||
(you won't need to run ez_setup.py).
|
||||
sudo python2 Downloads/get-pip.py
|
||||
sudo python2 -m pip install virtualenv
|
||||
|
||||
`get-pip.py`_
|
||||
On Windows, as an administrator:
|
||||
|
||||
To install the latest setuptools, you can use its bootstrap file:
|
||||
.. code-block:: bat
|
||||
|
||||
`ez_setup.py`_
|
||||
\Python27\python.exe Downloads\get-pip.py
|
||||
\Python27\python.exe -m pip install virtualenv
|
||||
|
||||
Either should be double-clickable once you download them. If you already have pip,
|
||||
you can upgrade them by running::
|
||||
|
||||
> pip install --upgrade pip setuptools
|
||||
|
||||
Most often, once you pull up a command prompt you want to be able to type :command:`pip`
|
||||
and :command:`python` which will run those things, but this might not automatically happen
|
||||
on Windows, because it doesn't know where those executables are (give either a try!).
|
||||
|
||||
To fix this, you should be able to navigate to your Python install directory
|
||||
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
|
||||
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
|
||||
check that you can now just type :command:`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.
|
||||
Now you can continue to :ref:`install-create-env`.
|
||||
|
||||
.. _virtualenv: https://virtualenv.pypa.io/
|
||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||
.. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Application Factories
|
|||
If you are already using packages and blueprints for your application
|
||||
(:ref:`blueprints`) there are a couple of really nice ways to further improve
|
||||
the experience. A common pattern is creating the application object when
|
||||
the blueprint is imported. But if you move the creation of this object,
|
||||
the blueprint is imported. But if you move the creation of this object
|
||||
into a function, you can then create multiple instances of this app later.
|
||||
|
||||
So why would you want to do this?
|
||||
|
|
@ -60,7 +60,7 @@ Factories & Extensions
|
|||
It's preferable to create your extensions and app factories so that the
|
||||
extension object does not initially get bound to the application.
|
||||
|
||||
Using `Flask-SQLAlchemy <http://pythonhosted.org/Flask-SQLAlchemy/>`_,
|
||||
Using `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_,
|
||||
as an example, you should not do something along those lines::
|
||||
|
||||
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
|
||||
have a Flask integration but it became unnecessary after some
|
||||
restructuring of the internals of Celery with Version 3. This guide fills
|
||||
in the blanks in how to properly use Celery with Flask but assumes that
|
||||
you generally already read the `First Steps with Celery
|
||||
<http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
|
||||
guide in the official Celery documentation.
|
||||
If your application has a long running task, such as processing some uploaded
|
||||
data or sending email, you don't want to wait for it to finish during a
|
||||
request. Instead, use a task queue to send the necessary data to another
|
||||
process that will run the task in the background while the request returns
|
||||
immediately.
|
||||
|
||||
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
|
||||
standard Python tools like :command:`pip` or :command:`easy_install`::
|
||||
Install
|
||||
-------
|
||||
|
||||
Celery is a separate Python package. Install it from PyPI using pip::
|
||||
|
||||
$ pip install celery
|
||||
|
||||
Configuring Celery
|
||||
------------------
|
||||
Configure
|
||||
---------
|
||||
|
||||
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`
|
||||
|
|
@ -36,15 +39,18 @@ This is all that is necessary to properly integrate Celery with Flask::
|
|||
from celery import Celery
|
||||
|
||||
def make_celery(app):
|
||||
celery = Celery(app.import_name, backend=app.config['CELERY_BACKEND'],
|
||||
broker=app.config['CELERY_BROKER_URL'])
|
||||
celery = Celery(
|
||||
app.import_name,
|
||||
backend=app.config['CELERY_RESULT_BACKEND'],
|
||||
broker=app.config['CELERY_BROKER_URL']
|
||||
)
|
||||
celery.conf.update(app.config)
|
||||
TaskBase = celery.Task
|
||||
class ContextTask(TaskBase):
|
||||
abstract = True
|
||||
|
||||
class ContextTask(celery.Task):
|
||||
def __call__(self, *args, **kwargs):
|
||||
with app.app_context():
|
||||
return TaskBase.__call__(self, *args, **kwargs)
|
||||
return self.run(*args, **kwargs)
|
||||
|
||||
celery.Task = ContextTask
|
||||
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
|
||||
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
|
||||
Flask::
|
||||
Let's write a task that adds two numbers together and returns the result. We
|
||||
configure Celery's broker and backend to use Redis, create a ``celery``
|
||||
application using the factor from above, and then use it to define the task. ::
|
||||
|
||||
from flask import Flask
|
||||
|
||||
|
|
@ -68,26 +75,27 @@ Flask::
|
|||
)
|
||||
celery = make_celery(flask_app)
|
||||
|
||||
|
||||
@celery.task()
|
||||
def add_together(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.wait()
|
||||
65
|
||||
result = add_together.delay(23, 42)
|
||||
result.wait() # 65
|
||||
|
||||
Running the Celery Worker
|
||||
-------------------------
|
||||
Run a worker
|
||||
------------
|
||||
|
||||
Now if you jumped in and already executed the above code you will be
|
||||
disappointed to learn that your ``.wait()`` will never actually return.
|
||||
That's because you also need to run celery. You can do that by running
|
||||
celery as a worker::
|
||||
If you jumped in and already executed the above code you will be
|
||||
disappointed to learn that ``.wait()`` will never actually return.
|
||||
That's because you also need to run a Celery worker to receive and execute the
|
||||
task. ::
|
||||
|
||||
$ celery -A your_application.celery worker
|
||||
|
||||
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
|
||||
==========================
|
||||
|
||||
One of the design principles of Flask is that response objects are created
|
||||
and passed down a chain of potential callbacks that can modify them or
|
||||
replace them. When the request handling starts, there is no response
|
||||
object yet. It is created as necessary either by a view function or by
|
||||
some other component in the system.
|
||||
One of the design principles of Flask is that response objects are created and
|
||||
passed down a chain of potential callbacks that can modify them or replace
|
||||
them. When the request handling starts, there is no response object yet. It is
|
||||
created as necessary either by a view function or by some other component in
|
||||
the system.
|
||||
|
||||
But what happens if you want to modify the response at a point where the
|
||||
response does not exist yet? A common example for that would be a
|
||||
before-request function that wants to set a cookie on the response object.
|
||||
What happens if you want to modify the response at a point where the response
|
||||
does not exist yet? A common example for that would be a
|
||||
:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
|
||||
response object.
|
||||
|
||||
One way is to avoid the situation. Very often that is possible. For
|
||||
instance you can try to move that logic into an after-request callback
|
||||
instead. Sometimes however moving that code there is just not a very
|
||||
pleasant experience or makes code look very awkward.
|
||||
One way is to avoid the situation. Very often that is possible. For instance
|
||||
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
||||
callback instead. However, sometimes moving code there makes it more
|
||||
more complicated or awkward to reason about.
|
||||
|
||||
As an alternative possibility you can attach a bunch of callback functions
|
||||
to the :data:`~flask.g` object and call them at the end of the request.
|
||||
This way you can defer code execution from anywhere in the application.
|
||||
|
||||
|
||||
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
|
||||
-------------------
|
||||
As an alternative, you can use :func:`~flask.after_this_request` to register
|
||||
callbacks that will execute after only the current request. This way you can
|
||||
defer code execution from anywhere in the application, based on the current
|
||||
request.
|
||||
|
||||
At any time during a request, we can register a function to be called at the
|
||||
end of the request. For example you can remember the current language of the
|
||||
user in a cookie in the before-request function::
|
||||
end of the request. For example you can remember the current language of the
|
||||
user in a cookie in a :meth:`~flask.Flask.before_request` callback::
|
||||
|
||||
from flask import request
|
||||
from flask import request, after_this_request
|
||||
|
||||
@app.before_request
|
||||
def detect_user_language():
|
||||
language = request.cookies.get('user_lang')
|
||||
|
||||
if language is None:
|
||||
language = guess_language_from_request()
|
||||
|
||||
# when the response exists, set a cookie with the language
|
||||
@after_this_request
|
||||
def remember_language(response):
|
||||
response.set_cookie('user_lang', language)
|
||||
|
||||
g.language = language
|
||||
|
|
|
|||
|
|
@ -39,10 +39,8 @@ the process, also read the :ref:`fabric-deployment` chapter.
|
|||
Basic Setup Script
|
||||
------------------
|
||||
|
||||
Because you have Flask running, you have setuptools available on your system anyways.
|
||||
Flask already depends upon setuptools. If you do not, fear not, there is a
|
||||
script to install it for you: `ez_setup.py`_. Just download and
|
||||
run with your Python interpreter.
|
||||
Because you have Flask installed, you have setuptools available on your system.
|
||||
Flask already depends upon setuptools.
|
||||
|
||||
Standard disclaimer applies: :ref:`you better use a virtualenv
|
||||
<virtualenv>`.
|
||||
|
|
@ -67,7 +65,7 @@ A basic :file:`setup.py` file for a Flask application looks like this::
|
|||
|
||||
Please keep in mind that you have to list subpackages explicitly. If you
|
||||
want setuptools to lookup the packages for you automatically, you can use
|
||||
the `find_packages` function::
|
||||
the ``find_packages`` function::
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
|
@ -76,12 +74,12 @@ the `find_packages` function::
|
|||
packages=find_packages()
|
||||
)
|
||||
|
||||
Most parameters to the `setup` function should be self explanatory,
|
||||
`include_package_data` and `zip_safe` might not be.
|
||||
`include_package_data` tells setuptools to look for a :file:`MANIFEST.in` file
|
||||
Most parameters to the ``setup`` function should be self explanatory,
|
||||
``include_package_data`` and ``zip_safe`` might not be.
|
||||
``include_package_data`` tells setuptools to look for a :file:`MANIFEST.in` file
|
||||
and install all the entries that match as package data. We will use this
|
||||
to distribute the static files and templates along with the Python module
|
||||
(see :ref:`distributing-resources`). The `zip_safe` flag can be used to
|
||||
(see :ref:`distributing-resources`). The ``zip_safe`` flag can be used to
|
||||
force or prevent zip Archive creation. In general you probably don't want
|
||||
your packages to be installed as zip files because some tools do not
|
||||
support them and they make debugging a lot harder.
|
||||
|
|
@ -123,13 +121,13 @@ your tarball::
|
|||
|
||||
Don't forget that even if you enlist them in your :file:`MANIFEST.in` file, they
|
||||
won't be installed for you unless you set the `include_package_data`
|
||||
parameter of the `setup` function to ``True``!
|
||||
parameter of the ``setup`` function to ``True``!
|
||||
|
||||
|
||||
Declaring Dependencies
|
||||
----------------------
|
||||
|
||||
Dependencies are declared in the `install_requires` parameter as a list.
|
||||
Dependencies are declared in the ``install_requires`` parameter as a list.
|
||||
Each item in that list is the name of a package that should be pulled from
|
||||
PyPI on installation. By default it will always use the most recent
|
||||
version, but you can also provide minimum and maximum version
|
||||
|
|
@ -159,21 +157,21 @@ Installing / Developing
|
|||
-----------------------
|
||||
|
||||
To install your application (ideally into a virtualenv) just run the
|
||||
:file:`setup.py` script with the `install` parameter. It will install your
|
||||
:file:`setup.py` script with the ``install`` parameter. It will install your
|
||||
application into the virtualenv's site-packages folder and also download
|
||||
and install all dependencies::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
If you are developing on the package and also want the requirements to be
|
||||
installed, you can use the `develop` command instead::
|
||||
installed, you can use the ``develop`` command instead::
|
||||
|
||||
$ python setup.py develop
|
||||
|
||||
This has the advantage of just installing a link to the site-packages
|
||||
folder instead of copying the data over. You can then continue to work on
|
||||
the code without having to run `install` again after each change.
|
||||
the code without having to run ``install`` again after each change.
|
||||
|
||||
|
||||
.. _pip: https://pypi.python.org/pypi/pip
|
||||
.. _Setuptools: https://pythonhosted.org/setuptools
|
||||
.. _Setuptools: https://pypi.python.org/pypi/setuptools
|
||||
|
|
|
|||
|
|
@ -47,37 +47,53 @@ even if the application behaves correctly:
|
|||
Error Handlers
|
||||
--------------
|
||||
|
||||
An error handler is a function, just like a view function, but it is
|
||||
called when an error happens and is passed that error. The error is most
|
||||
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
||||
can be a different error: a handler for internal server errors will be
|
||||
passed other exception instances as well if they are uncaught.
|
||||
An error handler is a function that returns a response when a type of error is
|
||||
raised, similar to how a view is a function that returns a response when a
|
||||
request URL is matched. It is passed the instance of the error being handled,
|
||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
|
||||
handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
||||
addition to explicit 500 errors.
|
||||
|
||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||
decorator and the error code of the exception. Keep in mind that Flask
|
||||
will *not* set the error code for you, so make sure to also provide the
|
||||
HTTP status code when returning a response.
|
||||
decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
|
||||
can be registered for a status code, like 404, or for an exception class.
|
||||
|
||||
Please note that if you add an error handler for "500 Internal Server
|
||||
Error", Flask will not trigger it if it's running in Debug mode.
|
||||
The status code of the response will not be set to the handler's code. Make
|
||||
sure to provide the appropriate HTTP status code when returning a response from
|
||||
a handler.
|
||||
|
||||
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
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# note that we set the 404 status explicitly
|
||||
return render_template('404.html'), 404
|
||||
|
||||
When using the :ref:`application factory pattern <app-factories>`::
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(404, page_not_found)
|
||||
return app
|
||||
|
||||
An example template might be this:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -49,5 +49,5 @@ web server's documentation.
|
|||
See also
|
||||
--------
|
||||
|
||||
* The `Favicon <http://en.wikipedia.org/wiki/Favicon>`_ article on
|
||||
* The `Favicon <https://en.wikipedia.org/wiki/Favicon>`_ article on
|
||||
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::
|
||||
|
||||
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
|
||||
|
||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||
|
|
@ -47,7 +47,7 @@ the file and redirects the user to the URL for the uploaded file::
|
|||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
|
||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
|
|
@ -58,7 +58,7 @@ the file and redirects the user to the URL for the uploaded file::
|
|||
return redirect(request.url)
|
||||
file = request.files['file']
|
||||
# if user does not select file, browser also
|
||||
# submit a empty part without filename
|
||||
# submit an empty part without filename
|
||||
if file.filename == '':
|
||||
flash('No selected file')
|
||||
return redirect(request.url)
|
||||
|
|
@ -71,9 +71,9 @@ the file and redirects the user to the URL for the uploaded file::
|
|||
<!doctype html>
|
||||
<title>Upload new File</title>
|
||||
<h1>Upload new File</h1>
|
||||
<form action="" method=post enctype=multipart/form-data>
|
||||
<p><input type=file name=file>
|
||||
<input type=submit value=Upload>
|
||||
<form method=post enctype=multipart/form-data>
|
||||
<input type=file name=file>
|
||||
<input type=submit value=Upload>
|
||||
</form>
|
||||
'''
|
||||
|
||||
|
|
@ -104,9 +104,9 @@ before storing it directly on the filesystem.
|
|||
>>> secure_filename('../../../../home/username/.bashrc')
|
||||
'home_username_.bashrc'
|
||||
|
||||
Now one last thing is missing: the serving of the uploaded files. In the
|
||||
:func:`upload_file()` we redirect the user to
|
||||
``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``.
|
||||
Now one last thing is missing: the serving of the uploaded files. In the
|
||||
:func:`upload_file()` we redirect the user to
|
||||
``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``.
|
||||
So we write the :func:`uploaded_file` function to return the file of that name. As
|
||||
of Flask 0.5 we can use a function that does that for us::
|
||||
|
||||
|
|
@ -181,4 +181,4 @@ applications dealing with uploads, there is also a Flask extension called
|
|||
blacklisting of extensions and more.
|
||||
|
||||
.. _jQuery: https://jquery.com/
|
||||
.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/
|
||||
.. _Flask-Uploads: https://pythonhosted.org/Flask-Uploads/
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ And here is the :file:`login.html` template which also inherits from
|
|||
{% if error %}
|
||||
<p class=error><strong>Error:</strong> {{ error }}
|
||||
{% endif %}
|
||||
<form action="" method=post>
|
||||
<form method=post>
|
||||
<dl>
|
||||
<dt>Username:
|
||||
<dd><input type=text name=username value="{{
|
||||
|
|
|
|||
|
|
@ -90,14 +90,19 @@ Then you can define your central place to combine the views like this::
|
|||
You can further optimize this in terms of amount of keystrokes needed to
|
||||
write this by having a function that calls into
|
||||
:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project
|
||||
name and a dot, and by wrapping `view_func` in a `LazyView` as needed::
|
||||
name and a dot, and by wrapping `view_func` in a `LazyView` as needed. ::
|
||||
|
||||
def url(url_rule, import_name, **options):
|
||||
def url(import_name, url_rules=[], **options):
|
||||
view = LazyView('yourapplication.' + import_name)
|
||||
app.add_url_rule(url_rule, view_func=view, **options)
|
||||
for url_rule in url_rules:
|
||||
app.add_url_rule(url_rule, view_func=view, **options)
|
||||
|
||||
url('/', 'views.index')
|
||||
url('/user/<username>', 'views.user')
|
||||
# add a single route to the index view
|
||||
url('views.index', ['/'])
|
||||
|
||||
# add two routes to a single function endpoint
|
||||
url_rules = ['/user/','/user/<username>']
|
||||
url('views.user', url_rules)
|
||||
|
||||
One thing to keep in mind is that before and after request handlers have
|
||||
to be in a file that is imported upfront to work properly on the first
|
||||
|
|
|
|||
|
|
@ -8,15 +8,19 @@ module. That is quite simple. Imagine a small application looks like
|
|||
this::
|
||||
|
||||
/yourapplication
|
||||
/yourapplication.py
|
||||
yourapplication.py
|
||||
/static
|
||||
/style.css
|
||||
style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.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
|
||||
---------------
|
||||
|
||||
|
|
@ -29,9 +33,9 @@ You should then end up with something like that::
|
|||
|
||||
/yourapplication
|
||||
/yourapplication
|
||||
/__init__.py
|
||||
__init__.py
|
||||
/static
|
||||
/style.css
|
||||
style.css
|
||||
/templates
|
||||
layout.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
|
||||
yourapplication/__init__.py`` will not work. Let's just say that Python
|
||||
does not want modules in packages to be the startup file. But that is not
|
||||
a big problem, just add a new file called :file:`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::
|
||||
|
||||
from yourapplication import app
|
||||
app.run(debug=True)
|
||||
from setuptools import setup
|
||||
|
||||
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
|
||||
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::
|
||||
|
||||
/yourapplication
|
||||
/runserver.py
|
||||
setup.py
|
||||
/yourapplication
|
||||
/__init__.py
|
||||
/views.py
|
||||
__init__.py
|
||||
views.py
|
||||
/static
|
||||
/style.css
|
||||
style.css
|
||||
/templates
|
||||
layout.html
|
||||
index.html
|
||||
|
|
@ -105,6 +134,7 @@ You should then end up with something like that::
|
|||
|
||||
|
||||
.. _working-with-modules:
|
||||
.. _the full src for this example here: https://github.com/pallets/flask/tree/master/examples/patterns/largerapp
|
||||
|
||||
Working with Blueprints
|
||||
-----------------------
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ if you want to get started quickly.
|
|||
You can download `Flask-SQLAlchemy`_ from `PyPI
|
||||
<https://pypi.python.org/pypi/Flask-SQLAlchemy>`_.
|
||||
|
||||
.. _Flask-SQLAlchemy: http://pythonhosted.org/Flask-SQLAlchemy/
|
||||
.. _Flask-SQLAlchemy: http://flask-sqlalchemy.pocoo.org/
|
||||
|
||||
|
||||
Declarative
|
||||
|
|
@ -108,9 +108,9 @@ Querying is simple as well:
|
|||
>>> User.query.filter(User.name == 'admin').first()
|
||||
<User u'admin'>
|
||||
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||
.. _declarative:
|
||||
http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
||||
https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
||||
|
||||
Manual Object Relational Mapping
|
||||
--------------------------------
|
||||
|
|
@ -135,7 +135,7 @@ Here is an example :file:`database.py` module for your application::
|
|||
def init_db():
|
||||
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
|
||||
application module::
|
||||
|
||||
|
|
@ -215,4 +215,4 @@ You can also pass strings of SQL statements to the
|
|||
(1, u'admin', u'admin@localhost')
|
||||
|
||||
For more information about SQLAlchemy, head over to the
|
||||
`website <http://www.sqlalchemy.org/>`_.
|
||||
`website <https://www.sqlalchemy.org/>`_.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
Using SQLite 3 with Flask
|
||||
=========================
|
||||
|
||||
In Flask you can easily implement the opening of database connections on
|
||||
demand and closing them when the context dies (usually at the end of the
|
||||
In Flask you can easily implement the opening of database connections on
|
||||
demand and closing them when the context dies (usually at the end of the
|
||||
request).
|
||||
|
||||
Here is a simple example of how you can use SQLite 3 with Flask::
|
||||
|
|
@ -71,7 +71,8 @@ Now in each request handling function you can access `g.db` to get the
|
|||
current open database connection. To simplify working with SQLite, a
|
||||
row factory function is useful. It is executed for every result returned
|
||||
from the database to convert the result. For instance, in order to get
|
||||
dictionaries instead of tuples, this could be inserted into ``get_db``::
|
||||
dictionaries instead of tuples, this could be inserted into the ``get_db``
|
||||
function we created above::
|
||||
|
||||
def make_dicts(cursor, row):
|
||||
return dict((cursor.description[idx][0], value)
|
||||
|
|
@ -79,21 +80,37 @@ dictionaries instead of tuples, this could be inserted into ``get_db``::
|
|||
|
||||
db.row_factory = make_dicts
|
||||
|
||||
Or even simpler::
|
||||
This will make the sqlite3 module return dicts for this database connection, which are much nicer to deal with. Even more simply, we could place this in ``get_db`` instead::
|
||||
|
||||
db.row_factory = sqlite3.Row
|
||||
|
||||
This would use Row objects rather than dicts to return the results of queries. These are ``namedtuple`` s, so we can access them either by index or by key. For example, assuming we have a ``sqlite3.Row`` called ``r`` for the rows ``id``, ``FirstName``, ``LastName``, and ``MiddleInitial``::
|
||||
|
||||
>>> # You can get values based on the row's name
|
||||
>>> r['FirstName']
|
||||
John
|
||||
>>> # Or, you can get them based on index
|
||||
>>> r[1]
|
||||
John
|
||||
# Row objects are also iterable:
|
||||
>>> for value in r:
|
||||
... print(value)
|
||||
1
|
||||
John
|
||||
Doe
|
||||
M
|
||||
|
||||
Additionally, it is a good idea to provide a query function that combines
|
||||
getting the cursor, executing and fetching the results::
|
||||
|
||||
|
||||
def query_db(query, args=(), one=False):
|
||||
cur = get_db().execute(query, args)
|
||||
rv = cur.fetchall()
|
||||
cur.close()
|
||||
return (rv[0] if rv else None) if one else rv
|
||||
|
||||
This handy little function, in combination with a row factory, makes
|
||||
working with the database much more pleasant than it is by just using the
|
||||
This handy little function, in combination with a row factory, makes
|
||||
working with the database much more pleasant than it is by just using the
|
||||
raw cursor and connection objects.
|
||||
|
||||
Here is how you can use it::
|
||||
|
|
@ -114,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
|
||||
the SQL statement with string formatting because this makes it possible
|
||||
to attack the application using `SQL Injections
|
||||
<http://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||
<https://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||
|
||||
Initial Schemas
|
||||
---------------
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ forms.
|
|||
fun. You can get it from `PyPI
|
||||
<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
|
||||
---------
|
||||
|
|
@ -108,7 +108,7 @@ takes advantage of the :file:`_formhelpers.html` template:
|
|||
.. sourcecode:: html+jinja
|
||||
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
<form method=post action="/register">
|
||||
<form method=post>
|
||||
<dl>
|
||||
{{ render_field(form.username) }}
|
||||
{{ render_field(form.email) }}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ So what did that code do?
|
|||
class will be our WSGI application.
|
||||
2. Next we create an instance of this class. The first argument is the name of
|
||||
the application's module or package. If you are using a single module (as
|
||||
in this example), you should use `__name__` because depending on if it's
|
||||
in this example), you should use ``__name__`` because depending on if it's
|
||||
started as application or imported as module the name will be different
|
||||
(``'__main__'`` versus the actual import name). This is needed so that
|
||||
Flask knows where to look for templates, static files, and so on. For more
|
||||
|
|
@ -42,17 +42,24 @@ your application :file:`flask.py` because this would conflict with Flask
|
|||
itself.
|
||||
|
||||
To run the application you can either use the :command:`flask` command or
|
||||
python's :option:`-m` switch with Flask. Before you can do that you need
|
||||
python's ``-m`` switch with Flask. Before you can do that you need
|
||||
to tell your terminal the application to work with by exporting the
|
||||
`FLASK_APP` environment variable::
|
||||
``FLASK_APP`` environment variable::
|
||||
|
||||
$ export FLASK_APP=hello.py
|
||||
$ flask run
|
||||
* 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::
|
||||
|
||||
Alternatively you can use `python -m flask`::
|
||||
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`::
|
||||
|
||||
$ export FLASK_APP=hello.py
|
||||
$ python -m flask run
|
||||
|
|
@ -86,7 +93,7 @@ should see your hello world greeting.
|
|||
What to do if the Server does not Start
|
||||
---------------------------------------
|
||||
|
||||
In case the ``python -m flask`` fails or :command:`flask` does not exist,
|
||||
In case the :command:`python -m flask` fails or :command:`flask` does not exist,
|
||||
there are multiple reasons this might be the case. First of all you need
|
||||
to look at the error message.
|
||||
|
||||
|
|
@ -95,17 +102,17 @@ Old Version of Flask
|
|||
|
||||
Versions of Flask older than 0.11 use to have different ways to start the
|
||||
application. In short, the :command:`flask` command did not exist, and
|
||||
neither did ``python -m flask``. In that case you have two options:
|
||||
neither did :command:`python -m flask`. In that case you have two options:
|
||||
either upgrade to newer Flask versions or have a look at the :ref:`server`
|
||||
docs to see the alternative method for running a server.
|
||||
|
||||
Invalid Import Name
|
||||
```````````````````
|
||||
|
||||
The :option:`-a` argument to :command:`flask` is the name of the module to
|
||||
import. In case that module is incorrectly named you will get an 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.
|
||||
The ``FLASK_APP`` environment variable is the name of the module to import at
|
||||
:command:`flask run`. In case that module is incorrectly named you will get an
|
||||
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.
|
||||
|
||||
The most common reason is a typo or because you did not actually create an
|
||||
``app`` object.
|
||||
|
|
@ -123,13 +130,13 @@ That is not very nice and Flask can do better. If you enable debug
|
|||
support the server will reload itself on code changes, and it will also
|
||||
provide you with a helpful debugger if things go wrong.
|
||||
|
||||
To enable debug mode you can export the `FLASK_DEBUG` environment variable
|
||||
To enable debug mode you can 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`).
|
||||
(On Windows you need to use ``set`` instead of ``export``).
|
||||
|
||||
This does the following things:
|
||||
|
||||
|
|
@ -153,20 +160,22 @@ Screenshot of the debugger in action:
|
|||
:class: screenshot
|
||||
: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`.
|
||||
|
||||
|
||||
Routing
|
||||
-------
|
||||
|
||||
Modern web applications have beautiful URLs. This helps people remember
|
||||
the URLs, which is especially handy for applications that are used from
|
||||
mobile devices with slower network connections. If the user can directly
|
||||
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.
|
||||
Modern web applications use meaningful URLs to help users. Users are more
|
||||
likely to like a page and come back if the page uses a meaningful URL they can
|
||||
remember and use to directly visit a page.
|
||||
|
||||
As you have seen above, the :meth:`~flask.Flask.route` decorator is used to
|
||||
bind a function to a URL. Here are some basic examples::
|
||||
Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
|
|
@ -176,16 +185,16 @@ bind a function to a URL. Here are some basic examples::
|
|||
def hello():
|
||||
return 'Hello, World'
|
||||
|
||||
But there is more to it! You can make certain parts of the URL dynamic and
|
||||
attach multiple rules to a function.
|
||||
You can do more! You can make parts of the URL dynamic and attach multiple
|
||||
rules to a function.
|
||||
|
||||
Variable Rules
|
||||
``````````````
|
||||
|
||||
To add variable parts to a URL you can mark these special sections as
|
||||
``<variable_name>``. Such a part is then passed as a keyword argument to your
|
||||
function. Optionally a converter can be used by specifying a rule with
|
||||
``<converter:variable_name>``. Here are some nice examples::
|
||||
You can add variable sections to a URL by marking sections with
|
||||
``<variable_name>``. Your function then receives the ``<variable_name>``
|
||||
as a keyword argument. Optionally, you can use a converter to specify the type
|
||||
of the argument like ``<converter:variable_name>``. ::
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def show_user_profile(username):
|
||||
|
|
@ -197,111 +206,111 @@ function. Optionally a converter can be used by specifying a rule with
|
|||
# show the post with the given id, the id is an integer
|
||||
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
|
||||
|
||||
=========== ===============================================
|
||||
`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
|
||||
=========== ===============================================
|
||||
Converter types:
|
||||
|
||||
.. 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
|
||||
behind that module is to ensure beautiful and unique URLs based on
|
||||
precedents laid down by Apache and earlier HTTP servers.
|
||||
Unique URLs / Redirection Behavior
|
||||
``````````````````````````````````
|
||||
|
||||
Take these two rules::
|
||||
Take these two rules::
|
||||
|
||||
@app.route('/projects/')
|
||||
def projects():
|
||||
return 'The project page'
|
||||
@app.route('/projects/')
|
||||
def projects():
|
||||
return 'The project page'
|
||||
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return 'The about page'
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return 'The about page'
|
||||
|
||||
Though they look rather similar, they differ in their use of the trailing
|
||||
slash in the URL *definition*. In the first case, the canonical URL for the
|
||||
`projects` endpoint has a trailing slash. In that sense, it is similar to
|
||||
a folder on a filesystem. Accessing it without a trailing slash will cause
|
||||
Flask to redirect to the canonical URL with the trailing slash.
|
||||
Though they look similar, they differ in their use of the trailing slash in
|
||||
the URL. In the first case, the canonical URL for the ``projects`` endpoint
|
||||
uses a trailing slash. It's similar to a folder in a file system; if you
|
||||
access the URL without a trailing slash, Flask redirects you to the
|
||||
canonical URL with the trailing slash.
|
||||
|
||||
In the second case, however, the URL is defined without a trailing slash,
|
||||
rather like the pathname of a file on UNIX-like systems. Accessing the URL
|
||||
with a trailing slash will produce 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.
|
||||
In the second case, however, the URL definition lacks a trailing slash,
|
||||
like the pathname of a file on UNIX-like systems. Accessing the URL with a
|
||||
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.
|
||||
|
||||
.. _url-building:
|
||||
|
||||
URL Building
|
||||
````````````
|
||||
|
||||
If it can match URLs, can Flask also generate them? Of course it can. To
|
||||
build a URL to a specific function you can use the :func:`~flask.url_for`
|
||||
function. It accepts the name of the function as first argument and a number
|
||||
of keyword arguments, each corresponding to the variable part of the URL rule.
|
||||
Unknown variable parts are appended to the URL as query parameters. Here are
|
||||
some examples:
|
||||
|
||||
>>> 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?next=/
|
||||
/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`).
|
||||
To build a URL to a specific function, use the :func:`~flask.url_for` function.
|
||||
It accepts the name of the function as its first argument and any number of
|
||||
keyword arguments, each corresponding to a variable part of the URL rule.
|
||||
Unknown variable parts are appended to the URL as query parameters.
|
||||
|
||||
Why would you want to build URLs using the URL reversing function
|
||||
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
||||
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.
|
||||
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'))
|
||||
|
||||
/
|
||||
/login
|
||||
/login?next=/
|
||||
/user/John%20Doe
|
||||
|
||||
HTTP Methods
|
||||
````````````
|
||||
|
||||
HTTP (the protocol web applications are speaking) knows different methods for
|
||||
accessing URLs. By default, a route only answers to ``GET`` requests, but that
|
||||
can be changed by providing the `methods` argument to the
|
||||
:meth:`~flask.Flask.route` decorator. Here are some examples::
|
||||
|
||||
from flask import request
|
||||
Web applications use different HTTP methods when accessing URLs. You should
|
||||
familiarize yourself with the HTTP methods as you work with Flask. By default,
|
||||
a route only answers to ``GET`` requests. You can use the ``methods`` argument
|
||||
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
|
||||
::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
|
|
@ -310,64 +319,11 @@ can be changed by providing the `methods` argument to the
|
|||
else:
|
||||
show_the_login_form()
|
||||
|
||||
If ``GET`` is present, ``HEAD`` will be added automatically for you. You
|
||||
don't have to deal with that. It will also make sure that ``HEAD`` requests
|
||||
are handled as the `HTTP RFC`_ (the document describing the HTTP
|
||||
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.
|
||||
If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
|
||||
and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
|
||||
``OPTIONS`` is automatically implemented for you.
|
||||
|
||||
You have no idea what an HTTP method is? Worry not, here is a quick
|
||||
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
|
||||
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
|
||||
|
||||
Static Files
|
||||
------------
|
||||
|
|
@ -446,22 +402,22 @@ know how that works, head over to the :ref:`template-inheritance` pattern
|
|||
documentation. Basically template inheritance makes it possible to keep
|
||||
certain elements on each page (like header, navigation and footer).
|
||||
|
||||
Automatic escaping is enabled, so if `name` contains HTML it will be escaped
|
||||
Automatic escaping is enabled, so if ``name`` contains HTML it will be escaped
|
||||
automatically. If you can trust a variable and you know that it will be
|
||||
safe HTML (for example because it came from a module that converts wiki
|
||||
markup to HTML) you can mark it as safe by using the
|
||||
:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the
|
||||
template. Head over to the Jinja 2 documentation for more examples.
|
||||
|
||||
Here is a basic introduction to how the :class:`~jinja2.Markup` class works:
|
||||
Here is a basic introduction to how the :class:`~jinja2.Markup` class works::
|
||||
|
||||
>>> from flask import Markup
|
||||
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
||||
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
|
||||
>>> Markup.escape('<blink>hacker</blink>')
|
||||
Markup(u'<blink>hacker</blink>')
|
||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||
u'Marked up \xbb HTML'
|
||||
>>> from flask import Markup
|
||||
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
||||
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
|
||||
>>> Markup.escape('<blink>hacker</blink>')
|
||||
Markup(u'<blink>hacker</blink>')
|
||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||
u'Marked up \xbb HTML'
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
|
||||
|
|
@ -538,16 +494,16 @@ The Request Object
|
|||
``````````````````
|
||||
|
||||
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
|
||||
the `flask` module::
|
||||
the ``flask`` module::
|
||||
|
||||
from flask import request
|
||||
|
||||
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
|
||||
: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::
|
||||
|
||||
@app.route('/login', methods=['POST', 'GET'])
|
||||
|
|
@ -563,14 +519,14 @@ attributes mentioned above::
|
|||
# was GET or the credentials were invalid
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
What happens if the key does not exist in the `form` attribute? In that
|
||||
What happens if the key does not exist in the ``form`` attribute? In that
|
||||
case a special :exc:`KeyError` is raised. You can catch it like a
|
||||
standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request
|
||||
error page is shown instead. So for many situations you don't have to
|
||||
deal with that problem.
|
||||
|
||||
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', '')
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
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
|
||||
|
|
@ -725,17 +681,15 @@ converting return values into response objects is as follows:
|
|||
3. If a tuple is returned the items in the tuple can provide extra
|
||||
information. Such tuples have to be in the form ``(response, status,
|
||||
headers)`` or ``(response, headers)`` where at least one item has
|
||||
to be in the tuple. The `status` value will override the status code
|
||||
and `headers` can be a list or dictionary of additional header values.
|
||||
to be in the tuple. The ``status`` value will override the status code
|
||||
and ``headers`` can be a list or dictionary of additional header values.
|
||||
4. If none of that works, Flask will assume the return value is a
|
||||
valid WSGI application and convert that into a response object.
|
||||
|
||||
If you want to get hold of the resulting response object inside the view
|
||||
you can use the :func:`~flask.make_response` function.
|
||||
|
||||
Imagine you have a view like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
Imagine you have a view like this::
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
|
|
@ -743,9 +697,7 @@ Imagine you have a view like this:
|
|||
|
||||
You just need to wrap the return expression with
|
||||
:func:`~flask.make_response` and get the response object to modify it, then
|
||||
return it:
|
||||
|
||||
.. sourcecode:: python
|
||||
return it::
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
|
|
@ -784,7 +736,7 @@ sessions work::
|
|||
session['username'] = request.form['username']
|
||||
return redirect(url_for('index'))
|
||||
return '''
|
||||
<form action="" method="post">
|
||||
<form method="post">
|
||||
<p><input type=text name=username>
|
||||
<p><input type=submit value=Login>
|
||||
</form>
|
||||
|
|
@ -807,13 +759,13 @@ not using the template engine (as in this example).
|
|||
The problem with random is that it's hard to judge what is truly random. And
|
||||
a secret key should be as random as possible. Your operating system
|
||||
has ways to generate pretty random stuff based on a cryptographic
|
||||
random generator which can be used to get such a key:
|
||||
random generator which can be used to get such a key::
|
||||
|
||||
>>> import os
|
||||
>>> os.urandom(24)
|
||||
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
|
||||
>>> import os
|
||||
>>> os.urandom(24)
|
||||
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
|
||||
|
||||
Just take that thing and copy/paste it into your code and you're done.
|
||||
Just take that thing and copy/paste it into your code and you're done.
|
||||
|
||||
A note on cookie-based sessions: Flask will take the values you put into the
|
||||
session object and serialize them into a cookie. If you are finding some
|
||||
|
|
@ -821,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
|
||||
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
|
||||
----------------
|
||||
|
|
|
|||
|
|
@ -119,9 +119,9 @@ understand what is actually happening. The new behavior is quite simple:
|
|||
not executed yet or at all (for example in test environments sometimes
|
||||
you might want to not execute before-request callbacks).
|
||||
|
||||
Now what happens on errors? In production mode if an exception is not
|
||||
caught, the 500 internal server handler is called. In development mode
|
||||
however the exception is not further processed and bubbles up to the WSGI
|
||||
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 debug mode
|
||||
however the exception is not further processed and bubbles up to the WSGI
|
||||
server. That way things like the interactive debugger can provide helpful
|
||||
debug information.
|
||||
|
||||
|
|
@ -214,10 +214,11 @@ provide you with important information.
|
|||
Starting with Flask 0.7 you have finer control over that behavior by
|
||||
setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By
|
||||
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
|
||||
as it will cause your application to leak memory on exceptions. However
|
||||
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,
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
tags. For more information on that have a look at the Wikipedia article
|
||||
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
|
||||
explicitly told otherwise. This should rule out all XSS problems caused
|
||||
|
|
@ -38,7 +38,7 @@ either double or single quotes when using Jinja expressions in them:
|
|||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<a href="{{ href }}">the text</a>
|
||||
<input value="{{ value }}">
|
||||
|
||||
Why is this necessary? Because if you would not be doing that, an
|
||||
attacker could easily inject custom JavaScript handlers. For example an
|
||||
|
|
@ -46,15 +46,26 @@ attacker could inject this piece of HTML+JavaScript:
|
|||
|
||||
.. sourcecode:: html
|
||||
|
||||
onmouseover=alert(document.cookie)
|
||||
onmouseover=alert(document.cookie)
|
||||
|
||||
When the user would then move with the mouse over the link, the cookie
|
||||
When the user would then move with the mouse over the input, the cookie
|
||||
would be presented to the user in an alert window. But instead of showing
|
||||
the cookie to the user, a good attacker might also execute any other
|
||||
JavaScript code. In combination with CSS injections the attacker might
|
||||
even make the element fill out the entire page so that the user would
|
||||
just have to have the mouse anywhere on the page to trigger the attack.
|
||||
|
||||
There is one class of XSS issues that Jinja's escaping does not protect
|
||||
against. The ``a`` tag's ``href`` attribute can contain a `javascript:` URI,
|
||||
which the browser will execute when clicked if not secured properly.
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<a href="{{ value }}">click here</a>
|
||||
<a href="javascript:alert('unsafe');">click here</a>
|
||||
|
||||
To prevent this, you'll need to set the :ref:`security-csp` response header.
|
||||
|
||||
Cross-Site Request Forgery (CSRF)
|
||||
---------------------------------
|
||||
|
||||
|
|
@ -104,3 +115,107 @@ vulnerabilities
|
|||
<https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so
|
||||
this behavior was changed and :func:`~flask.jsonify` now supports serializing
|
||||
arrays.
|
||||
|
||||
Security Headers
|
||||
----------------
|
||||
|
||||
Browsers recognize various response headers in order to control security. We
|
||||
recommend reviewing each of the headers below for use in your application.
|
||||
The `Flask-Talisman`_ extension can be used to manage HTTPS and the security
|
||||
headers for you.
|
||||
|
||||
.. _Flask-Talisman: https://github.com/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
|
||||
|
||||
.. _security-csp:
|
||||
|
||||
Content Security Policy (CSP)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tell the browser where it can load various types of resource from. This header
|
||||
should be used whenever possible, but requires some work to define the correct
|
||||
policy for your site. A very strict policy would be::
|
||||
|
||||
response.headers['Content-Security-Policy'] = "default-src 'self'"
|
||||
|
||||
- https://csp.withgoogle.com/docs/index.html
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||
|
||||
X-Content-Type-Options
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Forces the browser to honor the response content type instead of trying to
|
||||
detect it, which can be abused to generate a cross-site scripting (XSS)
|
||||
attack. ::
|
||||
|
||||
response.headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||
|
||||
X-Frame-Options
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Prevents external sites from embedding your site in an ``iframe``. This
|
||||
prevents a class of attacks where clicks in the outer frame can be translated
|
||||
invisibly to clicks on your page's elements. This is also known as
|
||||
"clickjacking". ::
|
||||
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
|
||||
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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 the template::
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ Docstring conventions:
|
|||
"""
|
||||
|
||||
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
|
||||
standard docstring::
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _templates:
|
||||
|
||||
Templates
|
||||
=========
|
||||
|
||||
|
|
|
|||
239
docs/testing.rst
239
docs/testing.rst
|
|
@ -5,23 +5,30 @@ Testing Flask Applications
|
|||
|
||||
**Something that is untested is broken.**
|
||||
|
||||
The origin of this quote is unknown and while it is not entirely correct, it is also
|
||||
not far from the truth. Untested applications make it hard to
|
||||
The origin of this quote is unknown and while it is not entirely correct, it
|
||||
is also not far from the truth. Untested applications make it hard to
|
||||
improve existing code and developers of untested applications tend to
|
||||
become pretty paranoid. If an application has automated tests, you can
|
||||
safely make changes and instantly know if anything breaks.
|
||||
|
||||
Flask provides a way to test your application by exposing the Werkzeug
|
||||
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
|
||||
we will use the :mod:`unittest` package that comes pre-installed with Python.
|
||||
You can then use that with your favourite testing solution.
|
||||
|
||||
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
|
||||
---------------
|
||||
|
||||
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
|
||||
sources from `the examples`_.
|
||||
source code from `the examples`_.
|
||||
|
||||
.. _the examples:
|
||||
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
||||
|
|
@ -29,90 +36,91 @@ sources from `the examples`_.
|
|||
The Testing Skeleton
|
||||
--------------------
|
||||
|
||||
In order to test the application, we add a second module
|
||||
(:file:`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
We begin by adding a tests directory under the application root. Then
|
||||
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 flaskr
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
import pytest
|
||||
|
||||
def setUp(self):
|
||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
flaskr.app.config['TESTING'] = True
|
||||
self.app = flaskr.app.test_client()
|
||||
with flaskr.app.app_context():
|
||||
flaskr.init_db()
|
||||
from flaskr import flaskr
|
||||
|
||||
def tearDown(self):
|
||||
os.close(self.db_fd)
|
||||
os.unlink(flaskr.app.config['DATABASE'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@pytest.fixture
|
||||
def client():
|
||||
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
flaskr.app.config['TESTING'] = True
|
||||
client = flaskr.app.test_client()
|
||||
|
||||
The code in the :meth:`~unittest.TestCase.setUp` method creates a new test
|
||||
client and initializes a new database. This function is called before
|
||||
each individual test function is run. To delete the database after the
|
||||
test, we close the file and remove it from the filesystem in the
|
||||
: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.
|
||||
with flaskr.app.app_context():
|
||||
flaskr.init_db()
|
||||
|
||||
This test client will give us a simple interface to the application. We can
|
||||
trigger test requests to the application, and the client will also keep track
|
||||
of cookies for us.
|
||||
yield client
|
||||
|
||||
Because SQLite3 is filesystem-based we can easily use the tempfile module
|
||||
os.close(db_fd)
|
||||
os.unlink(flaskr.app.config['DATABASE'])
|
||||
|
||||
This client fixture will be called by each individual test. It gives us a
|
||||
simple interface to the application, where we can trigger test requests to the
|
||||
application. The client will also keep track of cookies for us.
|
||||
|
||||
During setup, the ``TESTING`` config flag is activated. What
|
||||
this does is disable error catching during request handling, so that
|
||||
you get better error reports when performing test requests against the
|
||||
application.
|
||||
|
||||
Because SQLite3 is filesystem-based, we can easily use the :mod:`tempfile` module
|
||||
to create a temporary database and initialize it. The
|
||||
: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
|
||||
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.
|
||||
|
||||
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::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
$ pytest
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 0 tests in 0.000s
|
||||
================ test session starts ================
|
||||
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
|
||||
with an exception.
|
||||
|
||||
.. _pytest fixture:
|
||||
https://docs.pytest.org/en/latest/fixture.html
|
||||
|
||||
The First Test
|
||||
--------------
|
||||
|
||||
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
|
||||
access the root of the application (``/``). To do this, we add a new
|
||||
test method to our class, like this::
|
||||
access the root of the application (``/``). To do this, we add a new
|
||||
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):
|
||||
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
|
||||
rv = client.get('/')
|
||||
assert b'No entries here so far' in rv.data
|
||||
|
||||
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.
|
||||
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
|
||||
|
|
@ -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::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.034s
|
||||
$ pytest -v
|
||||
|
||||
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
|
||||
------------------
|
||||
|
|
@ -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
|
||||
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):
|
||||
return self.app.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
def login(client, username, password):
|
||||
return client.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), 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
|
||||
invalid credentials. Add this new test to the class::
|
||||
invalid credentials. Add this new test function::
|
||||
|
||||
def test_login_logout(self):
|
||||
rv = self.login('admin', 'default')
|
||||
assert 'You were logged in' in rv.data
|
||||
rv = self.logout()
|
||||
assert 'You were logged out' in rv.data
|
||||
rv = self.login('adminx', 'default')
|
||||
assert 'Invalid username' in rv.data
|
||||
rv = self.login('admin', 'defaultx')
|
||||
assert 'Invalid password' in rv.data
|
||||
def test_login_logout(client):
|
||||
"""Make sure login and logout works."""
|
||||
|
||||
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
||||
assert b'You were logged in' in rv.data
|
||||
|
||||
rv = logout(client)
|
||||
assert b'You were logged out' 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
|
||||
--------------------
|
||||
|
||||
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::
|
||||
|
||||
def test_messages(self):
|
||||
self.login('admin', 'default')
|
||||
rv = self.app.post('/add', data=dict(
|
||||
def test_messages(client):
|
||||
"""Test that messages work."""
|
||||
|
||||
login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
||||
rv = client.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
text='<strong>HTML</strong> allowed here'
|
||||
), follow_redirects=True)
|
||||
assert 'No entries here so far' not in rv.data
|
||||
assert '<Hello>' in rv.data
|
||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
||||
assert b'No entries here so far' not in rv.data
|
||||
assert b'<Hello>' 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,
|
||||
which is the intended behavior.
|
||||
|
||||
Running that should now give us three passing tests::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
...
|
||||
----------------------------------------------------------------------
|
||||
Ran 3 tests in 0.332s
|
||||
$ pytest -v
|
||||
|
||||
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
|
||||
`MiniTwit Example`_ from the sources which contains a larger test
|
||||
suite.
|
||||
|
||||
|
||||
.. _MiniTwit Example:
|
||||
https://github.com/pallets/flask/tree/master/examples/minitwit/
|
||||
|
||||
|
||||
Other Testing Tricks
|
||||
--------------------
|
||||
|
||||
|
|
@ -208,7 +230,7 @@ temporarily. With this you can access the :class:`~flask.request`,
|
|||
functions. Here is a full example that demonstrates this approach::
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with app.test_request_context('/?name=Peter'):
|
||||
|
|
@ -353,3 +375,34 @@ independently of the session backend used::
|
|||
Note that in this case you have to use the ``sess`` object instead of the
|
||||
:data:`flask.session` proxy. The object however itself will provide the
|
||||
same interface.
|
||||
|
||||
|
||||
Testing JSON APIs
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
Flask has great support for JSON, and is a popular choice for building JSON
|
||||
APIs. Making requests with JSON data and examining JSON data in responses is
|
||||
very convenient::
|
||||
|
||||
from flask import request, jsonify
|
||||
|
||||
@app.route('/api/auth')
|
||||
def auth():
|
||||
json_data = request.get_json()
|
||||
email = json_data['email']
|
||||
password = json_data['password']
|
||||
return jsonify(token=generate_token(email, password))
|
||||
|
||||
with app.test_client() as c:
|
||||
rv = c.post('/api/auth', json={
|
||||
'username': 'flask', 'password': 'secret'
|
||||
})
|
||||
json_data = rv.get_json()
|
||||
assert verify_token(email, json_data['token'])
|
||||
|
||||
Passing the ``json`` argument in the test client methods sets the request data
|
||||
to the JSON-serialized object and sets the content type to
|
||||
``application/json``. You can get the JSON data from the request or response
|
||||
with ``get_json``.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
.. _tutorial-css:
|
||||
|
||||
Step 7: Adding Style
|
||||
Step 8: Adding Style
|
||||
====================
|
||||
|
||||
Now that everything else works, it's time to add some style to the
|
||||
application. Just create a stylesheet called :file:`style.css` in the
|
||||
:file:`static` folder we created before:
|
||||
:file:`static` folder:
|
||||
|
||||
.. sourcecode:: css
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
.. _tutorial-dbcon:
|
||||
|
||||
Step 3: Database Connections
|
||||
Step 4: Database Connections
|
||||
----------------------------
|
||||
|
||||
We have created a function for establishing a database connection with
|
||||
`connect_db`, but by itself, that's not particularly useful. Creating and
|
||||
closing database connections all the time is very inefficient, so we want
|
||||
to keep it around for longer. Because database connections encapsulate a
|
||||
transaction, we also need to make sure that only one request at the time
|
||||
uses the connection. How can we elegantly do that with Flask?
|
||||
Let's continue building our code in the ``flaskr.py`` file.
|
||||
(Scroll to the end of the page for more about project layout.)
|
||||
|
||||
This is where the application context comes into play, so let's start
|
||||
there.
|
||||
You currently have a function for establishing a database connection with
|
||||
`connect_db`, but by itself, it is not particularly useful. Creating and
|
||||
closing database connections all the time is very inefficient, so you will
|
||||
need to keep it around for longer. Because database connections
|
||||
encapsulate a transaction, you will need to make sure that only one
|
||||
request at a time uses the connection. An elegant way to do this is by
|
||||
utilizing the *application context*.
|
||||
|
||||
Flask provides us with two contexts: the application context and the
|
||||
request context. For the time being, all you have to know is that there
|
||||
Flask provides two contexts: the *application context* and the
|
||||
*request context*. For the time being, all you have to know is that there
|
||||
are special variables that use these. For instance, the
|
||||
:data:`~flask.request` variable is the request object associated with
|
||||
the current request, whereas :data:`~flask.g` is a general purpose
|
||||
variable associated with the current application context. We will go into
|
||||
the details of this a bit later.
|
||||
variable associated with the current application context. The tutorial
|
||||
will cover some more details of this later on.
|
||||
|
||||
For the time being, all you have to know is that you can store information
|
||||
safely on the :data:`~flask.g` object.
|
||||
|
|
@ -37,7 +38,7 @@ already established connection::
|
|||
g.sqlite_db = connect_db()
|
||||
return g.sqlite_db
|
||||
|
||||
So now we know how to connect, but how do we properly disconnect? For
|
||||
Now you know how to connect, but how can you properly disconnect? For
|
||||
that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
|
||||
decorator. It's executed every time the application context tears down::
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.. _tutorial-dbinit:
|
||||
|
||||
Step 4: Creating The Database
|
||||
Step 5: Creating The Database
|
||||
=============================
|
||||
|
||||
As outlined earlier, Flaskr is a database powered application, and more
|
||||
|
|
@ -9,56 +9,64 @@ 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
|
||||
that schema.
|
||||
|
||||
Such a schema can be created by piping the ``schema.sql`` file into the
|
||||
`sqlite3` command as follows::
|
||||
Such a schema could be created by piping the ``schema.sql`` file into the
|
||||
``sqlite3`` command as follows::
|
||||
|
||||
sqlite3 /tmp/flaskr.db < schema.sql
|
||||
|
||||
The downside of this is that it requires the ``sqlite3`` command to be
|
||||
installed, which is not necessarily the case on every system. This also
|
||||
requires that we provide the path to the database, which can introduce
|
||||
errors. It's a good idea to add a function that initializes the database
|
||||
for you to the application.
|
||||
However, the downside of this is that it requires the ``sqlite3`` command
|
||||
to be installed, which is not necessarily the case on every system. This
|
||||
also requires that you provide the path to the database, which can introduce
|
||||
errors.
|
||||
|
||||
To do this, we can create a function and hook it into the :command:`flask`
|
||||
command that initializes the database. Let me show you the code first. Just
|
||||
add this function below the `connect_db` function in :file:`flaskr.py`::
|
||||
Instead of the ``sqlite3`` command above, it's a good idea to add a function
|
||||
to our application that initializes the database for you. To do this, you
|
||||
can create a function and hook it into a :command:`flask` command that
|
||||
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():
|
||||
db = get_db()
|
||||
|
||||
with app.open_resource('schema.sql', mode='r') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
@app.cli.command('initdb')
|
||||
def initdb_command():
|
||||
"""Initializes the database."""
|
||||
|
||||
init_db()
|
||||
print 'Initialized the database.'
|
||||
print('Initialized the database.')
|
||||
|
||||
The ``app.cli.command()`` decorator registers a new command with the
|
||||
:command:`flask` script. When the command executes, Flask will automatically
|
||||
create an application context for us bound to the right application.
|
||||
Within the function, we can then access :attr:`flask.g` and other things as
|
||||
we would expect. When the script ends, the application context tears down
|
||||
create an application context which is bound to the right application.
|
||||
Within the function, you can then access :attr:`flask.g` and other things as
|
||||
you might expect. When the script ends, the application context tears down
|
||||
and the database connection is released.
|
||||
|
||||
We want to keep an actual function around that initializes the database,
|
||||
You will want to keep an actual function around that initializes the database,
|
||||
though, so that we can easily create databases in unit tests later on. (For
|
||||
more information see :ref:`testing`.)
|
||||
|
||||
The :func:`~flask.Flask.open_resource` method of the application object
|
||||
is a convenient helper function that will open a resource that the
|
||||
application provides. This function opens a file from the resource
|
||||
location (your ``flaskr`` folder) and allows you to read from it. We are
|
||||
using this here to execute a script on the database connection.
|
||||
location (the :file:`flaskr/flaskr` folder) and allows you to read from it.
|
||||
It is used in this example to execute a script on the database connection.
|
||||
|
||||
The connection object provided by SQLite can give us a cursor object.
|
||||
On that cursor, there is a method to execute a complete script. Finally, we
|
||||
The connection object provided by SQLite can give you a cursor object.
|
||||
On that cursor, there is a method to execute a complete script. Finally, you
|
||||
only have to commit the changes. SQLite3 and other transactional
|
||||
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
|
||||
Initialized the database.
|
||||
|
|
|
|||
|
|
@ -3,21 +3,29 @@
|
|||
Step 0: Creating The Folders
|
||||
============================
|
||||
|
||||
Before we get started, let's create the folders needed for this
|
||||
application::
|
||||
It is recommended to install your Flask application within a virtualenv. Please
|
||||
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
|
||||
/static
|
||||
/templates
|
||||
/flaskr
|
||||
/static
|
||||
/templates
|
||||
|
||||
The ``flaskr`` folder is not a Python package, but just something where we
|
||||
drop our files. Later on, we will put our database schema as well as main
|
||||
module into this folder. It is done in the following way. The files inside
|
||||
the :file:`static` folder are 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 `Jinja2`_ templates. The
|
||||
templates you create later on in the tutorial will go in this directory.
|
||||
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
|
||||
how to run ``flaskr`` later on in this tutorial.
|
||||
|
||||
Continue with :ref:`tutorial-schema`.
|
||||
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
|
||||
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
|
||||
`Jinja2`_ templates. You will see examples of this later on.
|
||||
|
||||
For now you should continue with :ref:`tutorial-schema`.
|
||||
|
||||
.. _Jinja2: http://jinja.pocoo.org/
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
Tutorial
|
||||
========
|
||||
|
||||
You want to develop an application with Python and Flask? Here you have
|
||||
the chance to learn by example. In this tutorial, we will create a simple
|
||||
microblogging application. It only supports one user that can create
|
||||
text-only entries and there are no feeds or comments, but it still
|
||||
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
|
||||
else you need.
|
||||
Learn by example to develop an application with Python and Flask.
|
||||
|
||||
In this tutorial, we will create a simple blogging application. It only
|
||||
supports one user, only allows text entries, and has no feeds or comments.
|
||||
|
||||
While very simple, this example still features everything you need to get
|
||||
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
|
||||
the `example source`_.
|
||||
|
||||
.. _example source:
|
||||
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
||||
.. _example source: https://github.com/pallets/flask/tree/master/examples/flaskr/
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
@ -24,6 +24,7 @@ the `example source`_.
|
|||
folders
|
||||
schema
|
||||
setup
|
||||
packaging
|
||||
dbcon
|
||||
dbinit
|
||||
views
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
Introducing Flaskr
|
||||
==================
|
||||
|
||||
We will call our blogging application Flaskr, but feel free to choose your own
|
||||
less Web-2.0-ish name ;) Essentially, we want it to do the following things:
|
||||
This tutorial will demonstrate a blogging application named Flaskr, but feel
|
||||
free to choose your own less Web-2.0-ish name ;) Essentially, it will do the
|
||||
following things:
|
||||
|
||||
1. Let the user sign in and out with credentials specified in the
|
||||
configuration. Only one user is supported.
|
||||
|
|
@ -14,14 +15,14 @@ less Web-2.0-ish name ;) Essentially, we want it to do the following things:
|
|||
3. The index page shows all entries so far in reverse chronological order
|
||||
(newest on top) and the user can add new ones from there if logged in.
|
||||
|
||||
We will be using SQLite3 directly for this application because it's good
|
||||
enough for an application of this size. For larger applications, however,
|
||||
SQLite3 will be used directly for this application because it's good enough
|
||||
for an application of this size. For larger applications, however,
|
||||
it makes a lot of sense to use `SQLAlchemy`_, as it handles database
|
||||
connections in a more intelligent way, allowing you to target different
|
||||
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.
|
||||
|
||||
Here a screenshot of the final application:
|
||||
Here is a screenshot of the final application:
|
||||
|
||||
.. image:: ../_static/flaskr.png
|
||||
:align: center
|
||||
|
|
@ -30,4 +31,4 @@ Here a screenshot of the final application:
|
|||
|
||||
Continue with :ref:`tutorial-folders`.
|
||||
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||
|
|
|
|||
106
docs/tutorial/packaging.rst
Normal file
106
docs/tutorial/packaging.rst
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
.. _tutorial-packaging:
|
||||
|
||||
Step 3: Installing flaskr as a Package
|
||||
======================================
|
||||
|
||||
Flask is now shipped with built-in support for `Click`_. Click provides
|
||||
Flask with enhanced and extensible command line utilities. Later in this
|
||||
tutorial you will see exactly how to extend the ``flask`` command line
|
||||
interface (CLI).
|
||||
|
||||
A useful pattern to manage a Flask application is to install your app
|
||||
following the `Python Packaging Guide`_. Presently this involves
|
||||
creating two new files; :file:`setup.py` and :file:`MANIFEST.in` in the
|
||||
projects root directory. You also need to add an :file:`__init__.py`
|
||||
file to make the :file:`flaskr/flaskr` directory a package. After these
|
||||
changes, your code structure should be::
|
||||
|
||||
/flaskr
|
||||
/flaskr
|
||||
__init__.py
|
||||
/static
|
||||
/templates
|
||||
flaskr.py
|
||||
schema.sql
|
||||
setup.py
|
||||
MANIFEST.in
|
||||
|
||||
Create the ``setup.py`` file for ``flaskr`` with the following content::
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='flaskr',
|
||||
packages=['flaskr'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'flask',
|
||||
],
|
||||
)
|
||||
|
||||
When using setuptools, it is also necessary to specify any special files
|
||||
that should be included in your package (in the :file:`MANIFEST.in`).
|
||||
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 following lines::
|
||||
|
||||
graft flaskr/templates
|
||||
graft flaskr/static
|
||||
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
|
||||
is recommended to install your Flask application within a `virtualenv`_.
|
||||
With that said, from the ``flaskr/`` directory, go ahead and install the
|
||||
application with::
|
||||
|
||||
pip install --editable .
|
||||
|
||||
The above installation command assumes that it is run within the projects
|
||||
root directory, ``flaskr/``. The ``editable`` flag allows editing
|
||||
source code without having to reinstall the Flask app each time you make
|
||||
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.
|
||||
Do this on Mac or Linux with the following commands in ``flaskr/``::
|
||||
|
||||
export FLASK_APP=flaskr
|
||||
export FLASK_DEBUG=true
|
||||
flask run
|
||||
|
||||
(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.
|
||||
*Never leave debug mode activated in a production system*, because it will
|
||||
allow users to execute code on the server!
|
||||
|
||||
You will see a message telling you that server has started along with
|
||||
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
|
||||
because we don't have any views yet. That will be addressed a little later,
|
||||
but first, you should get the database working.
|
||||
|
||||
.. admonition:: Externally Visible Server
|
||||
|
||||
Want your server to be publicly available? Check out the
|
||||
:ref:`externally visible server <public-server>` section for more
|
||||
information.
|
||||
|
||||
Continue with :ref:`tutorial-dbcon`.
|
||||
|
||||
.. _Click: http://click.pocoo.org
|
||||
.. _Python Packaging Guide: https://packaging.python.org
|
||||
.. _virtualenv: https://virtualenv.pypa.io
|
||||
|
|
@ -3,10 +3,10 @@
|
|||
Step 1: Database Schema
|
||||
=======================
|
||||
|
||||
First, we want to create the database schema. Only a single table is needed
|
||||
for this application and we only want to support SQLite, so creating the
|
||||
database schema is quite easy. Just put the following contents into a file
|
||||
named `schema.sql` in the just created `flaskr` folder:
|
||||
In this step, you will create the database schema. Only a single table is
|
||||
needed for this application and it will only support SQLite. All you need to do
|
||||
is put the following contents into a file named :file:`schema.sql` in the
|
||||
:file:`flaskr/flaskr` folder:
|
||||
|
||||
.. sourcecode:: sql
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ named `schema.sql` in the just created `flaskr` folder:
|
|||
'text' text not null
|
||||
);
|
||||
|
||||
This schema consists of a single table called ``entries``. Each row in
|
||||
This schema consists of a single table called ``entries``. Each row in
|
||||
this table has an ``id``, a ``title``, and a ``text``. The ``id`` is an
|
||||
automatically incrementing integer and a primary key, the other two are
|
||||
strings that must not be null.
|
||||
|
|
|
|||
|
|
@ -3,28 +3,34 @@
|
|||
Step 2: Application Setup Code
|
||||
==============================
|
||||
|
||||
Now that we have the schema in place, we can create the application module.
|
||||
Let's call it ``flaskr.py``. We will place this file inside the ``flaskr``
|
||||
folder. We will begin by adding the imports we need and by adding the config
|
||||
section. For small applications, it is possible to drop the configuration
|
||||
directly into the module, and this is what we will be doing here. However,
|
||||
a cleaner solution would be to create a separate ``.ini`` or ``.py`` file,
|
||||
load that, and import the values from there.
|
||||
Next, we will create the application module, :file:`flaskr.py`. Just like the
|
||||
:file:`schema.sql` file you created in the previous step, this file should be
|
||||
placed inside of the :file:`flaskr/flaskr` folder.
|
||||
|
||||
First, we add the imports in :file:`flaskr.py`::
|
||||
For this tutorial, all the Python code we use will be put into this file
|
||||
(except for one line in ``__init__.py``, and any testing or optional files you
|
||||
decide to create).
|
||||
|
||||
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`)::
|
||||
|
||||
# all the imports
|
||||
import os
|
||||
import sqlite3
|
||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||
render_template, flash
|
||||
|
||||
Next, we can create our actual application and initialize it with the
|
||||
config from the same file in :file:`flaskr.py`::
|
||||
from flask import (Flask, request, session, g, redirect, url_for, abort,
|
||||
render_template, flash)
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
The next couple lines will create the actual application instance and
|
||||
initialize it with the config from the same file in :file:`flaskr.py`::
|
||||
|
||||
app = Flask(__name__) # create the application instance :)
|
||||
app.config.from_object(__name__) # load config from this file , flaskr.py
|
||||
|
||||
# Load default config and override config from an environment variable
|
||||
app.config.update(dict(
|
||||
|
|
@ -35,8 +41,8 @@ config from the same file in :file:`flaskr.py`::
|
|||
))
|
||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||
|
||||
The :class:`~flask.Config` object works similarly to a dictionary so we
|
||||
can update it with new values.
|
||||
In the above code, the :class:`~flask.Config` object works similarly to a
|
||||
dictionary, so it can be updated with new values.
|
||||
|
||||
.. admonition:: Database Path
|
||||
|
||||
|
|
@ -55,16 +61,16 @@ can update it with new values.
|
|||
|
||||
Usually, it is a good idea to load a separate, environment-specific
|
||||
configuration file. Flask allows you to import multiple configurations and it
|
||||
will use the setting defined in the last import. This enables robust
|
||||
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this.
|
||||
|
||||
.. code-block:: python
|
||||
will use the setting defined in the last import. This enables robust
|
||||
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve
|
||||
this. ::
|
||||
|
||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||
|
||||
Simply define the environment variable :envvar:`FLASKR_SETTINGS` that points to
|
||||
a config file to be loaded. The silent switch just tells Flask to not complain
|
||||
if no such environment key is set.
|
||||
If you want to do this (not required for this tutorial) simply define the
|
||||
environment variable :envvar:`FLASKR_SETTINGS` that points to a config file
|
||||
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`
|
||||
method on the config object and provide it with an import name of a
|
||||
|
|
@ -74,45 +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.
|
||||
Choose that key wisely and as hard to guess and complex as possible.
|
||||
|
||||
We will also add a method that allows for easy connections to the
|
||||
specified database. 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. We create a simple database connection through SQLite and
|
||||
then tell it to use the :class:`sqlite3.Row` object to represent rows.
|
||||
This allows us to treat the rows as if they were dictionaries instead of
|
||||
tuples.
|
||||
|
||||
::
|
||||
Lastly, add a method that allows for easy connections to the specified
|
||||
database. ::
|
||||
|
||||
def connect_db():
|
||||
"""Connects to the specific database."""
|
||||
|
||||
rv = sqlite3.connect(app.config['DATABASE'])
|
||||
rv.row_factory = sqlite3.Row
|
||||
return rv
|
||||
|
||||
With that out of the way, you should be able to start up the application
|
||||
without problems. Do this with the following commands::
|
||||
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.
|
||||
|
||||
export FLASK_APP=flaskr
|
||||
export FLASK_DEBUG=1
|
||||
flask run
|
||||
In the next section you will see how to run the application.
|
||||
|
||||
(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.
|
||||
*Never leave debug mode activated in a production system*, because it will
|
||||
allow users to execute code on the server!
|
||||
|
||||
You will see a message telling you that server has started along with
|
||||
the address at which you can access it.
|
||||
|
||||
When you head over to the server in your browser, you will get a 404 error
|
||||
because we don't have any views yet. We will focus on that a little later,
|
||||
but first, we should get the database working.
|
||||
|
||||
.. admonition:: Externally Visible Server
|
||||
|
||||
Want your server to be publicly available? Check out the
|
||||
:ref:`externally visible server <public-server>` section for more
|
||||
information.
|
||||
|
||||
Continue with :ref:`tutorial-dbcon`.
|
||||
Continue with :ref:`tutorial-packaging`.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
.. _tutorial-templates:
|
||||
|
||||
Step 6: The Templates
|
||||
Step 7: The Templates
|
||||
=====================
|
||||
|
||||
Now we should start working on the templates. If we were to request the URLs
|
||||
now, we would only get an exception that Flask cannot find the templates. The
|
||||
templates are using `Jinja2`_ syntax and have autoescaping enabled by
|
||||
Now it is time to start working on the templates. As you may have
|
||||
noticed, if you make requests with the app running, you will get
|
||||
an exception that Flask cannot find the templates. The templates
|
||||
are using `Jinja2`_ syntax and have autoescaping enabled by
|
||||
default. This means that unless you mark a value in the code with
|
||||
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
||||
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
||||
|
|
@ -14,7 +15,8 @@ escaped with their XML equivalents.
|
|||
We are also using template inheritance which makes it possible to reuse
|
||||
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
|
||||
|
||||
|
|
@ -57,9 +59,9 @@ show_entries.html
|
|||
|
||||
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
|
||||
in with the :func:`~flask.render_template` function. We also tell the
|
||||
form to submit to your `add_entry` function and use ``POST`` as HTTP
|
||||
method:
|
||||
in with the :func:`~flask.render_template` function. Notice that the form is
|
||||
configured to submit to the `add_entry` view function and use ``POST`` as
|
||||
HTTP method:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
|
|
@ -78,9 +80,9 @@ method:
|
|||
{% endif %}
|
||||
<ul class=entries>
|
||||
{% for entry in entries %}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
|
||||
{% else %}
|
||||
<li><em>Unbelievable. No entries here so far</em>
|
||||
<li><em>Unbelievable. No entries here so far</em></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -8,3 +8,89 @@ expected, it's probably not a bad idea to add automated tests to simplify
|
|||
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
|
||||
documentation. Go there to see how easy it is to test Flask applications.
|
||||
|
||||
Adding tests to flaskr
|
||||
----------------------
|
||||
|
||||
Assuming you have seen the :ref:`testing` section and have either written
|
||||
your own tests for ``flaskr`` or have followed along with the examples
|
||||
provided, you might be wondering about ways to organize the project.
|
||||
|
||||
One possible and recommended project structure is::
|
||||
|
||||
flaskr/
|
||||
flaskr/
|
||||
__init__.py
|
||||
static/
|
||||
templates/
|
||||
tests/
|
||||
test_flaskr.py
|
||||
setup.py
|
||||
MANIFEST.in
|
||||
|
||||
For now go ahead a create the :file:`tests/` directory as well as the
|
||||
:file:`test_flaskr.py` file.
|
||||
|
||||
Running the tests
|
||||
-----------------
|
||||
|
||||
At this point you can run the tests. Here ``pytest`` will be used.
|
||||
|
||||
.. 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::
|
||||
|
||||
pip install -e .
|
||||
pip install pytest
|
||||
|
||||
Run and watch the tests pass, within the top-level :file:`flaskr/`
|
||||
directory as::
|
||||
|
||||
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
|
||||
|
||||
setup(
|
||||
name='flaskr',
|
||||
packages=['flaskr'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'flask',
|
||||
],
|
||||
setup_requires=[
|
||||
'pytest-runner',
|
||||
],
|
||||
tests_require=[
|
||||
'pytest',
|
||||
],
|
||||
)
|
||||
|
||||
Now create :file:`setup.cfg` in the project root (alongside
|
||||
:file:`setup.py`)::
|
||||
|
||||
[aliases]
|
||||
test=pytest
|
||||
|
||||
Now you can run::
|
||||
|
||||
python setup.py test
|
||||
|
||||
This calls on the alias created in :file:`setup.cfg` which in turn runs
|
||||
``pytest`` via ``pytest-runner``, as the :file:`setup.py` script has
|
||||
been called. (Recall the `setup_requires` argument in :file:`setup.py`)
|
||||
Following the standard rules of test-discovery your tests will be
|
||||
found, run, and hopefully pass.
|
||||
|
||||
This is one possible way to run and manage testing. Here ``pytest`` is
|
||||
used, but there are other options such as ``nose``. Integrating testing
|
||||
with ``setuptools`` is convenient because it is not necessary to actually
|
||||
download ``pytest`` or any other testing framework one might use.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
.. _tutorial-views:
|
||||
|
||||
Step 5: The View Functions
|
||||
Step 6: The View Functions
|
||||
==========================
|
||||
|
||||
Now that the database connections are working, we can start writing the
|
||||
view functions. We will need four of them:
|
||||
Now that the database connections are working, you can start writing the
|
||||
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
|
||||
------------
|
||||
|
|
@ -30,7 +31,7 @@ Add New Entry
|
|||
|
||||
This view lets the user add new entries if they are logged in. This only
|
||||
responds to ``POST`` requests; the actual form is shown on the
|
||||
`show_entries` page. If everything worked out well, we will
|
||||
`show_entries` page. If everything worked out well, it will
|
||||
:func:`~flask.flash` an information message to the next request and
|
||||
redirect back to the `show_entries` page::
|
||||
|
||||
|
|
@ -45,8 +46,8 @@ redirect back to the `show_entries` page::
|
|||
flash('New entry was successfully posted')
|
||||
return redirect(url_for('show_entries'))
|
||||
|
||||
Note that we check that the user is logged in here (the `logged_in` key is
|
||||
present in the session and ``True``).
|
||||
Note that this view checks that the user is logged in (that is, if the
|
||||
`logged_in` key is present in the session and ``True``).
|
||||
|
||||
.. admonition:: Security Note
|
||||
|
||||
|
|
@ -81,11 +82,11 @@ notified about that, and the user is asked again::
|
|||
return render_template('login.html', error=error)
|
||||
|
||||
The `logout` function, on the other hand, removes that key from the session
|
||||
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
||||
again. There is a neat trick here: if you use the :meth:`~dict.pop` method
|
||||
of the dict and pass a second parameter to it (the default), the method
|
||||
will delete the key from the dictionary if present or do nothing when that
|
||||
key is not in there. This is helpful because now we don't have to check
|
||||
if the user was logged in.
|
||||
key is not in there. This is helpful because now it is not necessary to
|
||||
check if the user was logged in.
|
||||
|
||||
::
|
||||
|
||||
|
|
@ -97,21 +98,21 @@ if the user was logged in.
|
|||
|
||||
.. admonition:: Security Note
|
||||
|
||||
Passwords should never be stored in plain text in a production
|
||||
system. This tutorial uses plain text passwords for simplicity. If you
|
||||
plan to release a project based off this tutorial out into the world,
|
||||
passwords should be both `hashed and salted`_ before being stored in a
|
||||
database or file.
|
||||
Passwords should never be stored in plain text in a production
|
||||
system. This tutorial uses plain text passwords for simplicity. If you
|
||||
plan to release a project based off this tutorial out into the world,
|
||||
passwords should be both `hashed and salted`_ before being stored in a
|
||||
database or file.
|
||||
|
||||
Fortunately, there are Flask extensions for the purpose of
|
||||
hashing passwords and verifying passwords against hashes, so adding
|
||||
this functionality is fairly straight forward. There are also
|
||||
Fortunately, there are Flask extensions for the purpose of
|
||||
hashing passwords and verifying passwords against hashes, so adding
|
||||
this functionality is fairly straight forward. There are also
|
||||
many general python libraries that can be used for hashing.
|
||||
|
||||
You can find a list of recommended Flask extensions
|
||||
You can find a list of recommended Flask extensions
|
||||
`here <http://flask.pocoo.org/extensions/>`_
|
||||
|
||||
|
||||
Continue with :ref:`tutorial-templates`.
|
||||
|
||||
.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/
|
||||
.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/
|
||||
|
|
|
|||
|
|
@ -14,12 +14,50 @@ This section of the documentation enumerates all the changes in Flask from
|
|||
release to release and how you can change your code to have a painless
|
||||
updating experience.
|
||||
|
||||
If you want to use the :command:`easy_install` command to upgrade your Flask
|
||||
installation, make sure to pass it the :option:`-U` parameter::
|
||||
Use the :command:`pip` command to upgrade your existing Flask installation by
|
||||
providing the ``--upgrade`` parameter::
|
||||
|
||||
$ easy_install -U 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
|
||||
------------
|
||||
|
|
@ -30,7 +68,7 @@ to the release we decided to push out a 0.11 release first with some
|
|||
changes removed to make the transition easier. If you have been tracking
|
||||
the master branch which was 1.0 you might see some unexpected changes.
|
||||
|
||||
In case you did track the master branch you will notice that `flask --app`
|
||||
In case you did track the master branch you will notice that :command:`flask --app`
|
||||
is removed now. You need to use the environment variable to specify an
|
||||
application.
|
||||
|
||||
|
|
@ -68,7 +106,7 @@ Templating
|
|||
The :func:`~flask.templating.render_template_string` function has changed to
|
||||
autoescape template variables by default. This better matches the behavior
|
||||
of :func:`~flask.templating.render_template`.
|
||||
|
||||
|
||||
Extension imports
|
||||
`````````````````
|
||||
|
||||
|
|
@ -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
|
||||
:attr:`flask.Flask.app_ctx_globals_class`.
|
||||
|
||||
.. _Flask-OldSessions: http://pythonhosted.org/Flask-OldSessions/
|
||||
.. _Flask-OldSessions: https://pythonhosted.org/Flask-OldSessions/
|
||||
|
||||
Version 0.9
|
||||
-----------
|
||||
|
|
@ -133,7 +171,7 @@ Version 0.8
|
|||
-----------
|
||||
|
||||
Flask introduced a new session interface system. We also noticed that
|
||||
there was a naming collision between `flask.session` the module that
|
||||
there was a naming collision between ``flask.session`` the module that
|
||||
implements sessions and :data:`flask.session` which is the global session
|
||||
object. With that introduction we moved the implementation details for
|
||||
the session system into a new module called :mod:`flask.sessions`. If you
|
||||
|
|
@ -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
|
||||
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
|
||||
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
|
||||
|
|
@ -199,7 +237,7 @@ Please note that deprecation warnings are disabled by default starting
|
|||
with Python 2.7. In order to see the deprecation warnings that might be
|
||||
emitted you have to enabled them with the :mod:`warnings` module.
|
||||
|
||||
If you are working with windows and you lack the `patch` command line
|
||||
If you are working with windows and you lack the ``patch`` command line
|
||||
utility you can get it as part of various Unix runtime environments for
|
||||
windows including cygwin, msysgit or ming32. Also source control systems
|
||||
like svn, hg or git have builtin support for applying unified diffs as
|
||||
|
|
@ -316,7 +354,7 @@ to upgrade. What changed?
|
|||
runtime.
|
||||
- Blueprints have an inverse behavior for :meth:`url_for`. Previously
|
||||
``.foo`` told :meth:`url_for` that it should look for the endpoint
|
||||
`foo` on the application. Now it means “relative to current module”.
|
||||
``foo`` on the application. Now it means “relative to current module”.
|
||||
The script will inverse all calls to :meth:`url_for` automatically for
|
||||
you. It will do this in a very eager way so you might end up with
|
||||
some unnecessary leading dots in your code if you're not using
|
||||
|
|
@ -334,7 +372,7 @@ to upgrade. What changed?
|
|||
name into that folder if you want :file:`blueprintname/template.html` as
|
||||
the template name.
|
||||
|
||||
If you continue to use the `Module` object which is deprecated, Flask will
|
||||
If you continue to use the ``Module`` object which is deprecated, Flask will
|
||||
restore the previous behavior as good as possible. However we strongly
|
||||
recommend upgrading to the new blueprints as they provide a lot of useful
|
||||
improvement such as the ability to attach a blueprint multiple times,
|
||||
|
|
@ -354,7 +392,7 @@ change the order.
|
|||
|
||||
Another change that breaks backwards compatibility is that context
|
||||
processors will no longer override values passed directly to the template
|
||||
rendering function. If for example `request` is as variable passed
|
||||
rendering function. If for example ``request`` is as variable passed
|
||||
directly to the template, the default context processor will not override
|
||||
it with the current request object. This makes it easier to extend
|
||||
context processors later to inject additional variables without breaking
|
||||
|
|
@ -380,7 +418,7 @@ The following changes may be relevant to your application:
|
|||
for this feature. Removing support for this makes the Flask internal
|
||||
code easier to understand and fixes a couple of small issues that make
|
||||
debugging harder than necessary.
|
||||
- The `create_jinja_loader` function is gone. If you want to customize
|
||||
- The ``create_jinja_loader`` function is gone. If you want to customize
|
||||
the Jinja loader now, use the
|
||||
:meth:`~flask.Flask.create_jinja_environment` method instead.
|
||||
|
||||
|
|
|
|||
1
examples/flaskr/.gitignore
vendored
1
examples/flaskr/.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
flaskr.db
|
||||
.eggs/
|
||||
|
|
|
|||
3
examples/flaskr/MANIFEST.in
Normal file
3
examples/flaskr/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
graft flaskr/templates
|
||||
graft flaskr/static
|
||||
include flaskr/schema.sql
|
||||
|
|
@ -9,19 +9,25 @@
|
|||
|
||||
~ 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
|
||||
pointing to a configuration file.
|
||||
pointing to a configuration file or pass in a
|
||||
dictionary with config values using the create_app
|
||||
function.
|
||||
|
||||
2. Instruct flask to use the right application
|
||||
2. install the app from the root of the project directory
|
||||
|
||||
export FLASK_APP=flaskr
|
||||
pip install --editable .
|
||||
|
||||
3. initialize the database with this command:
|
||||
3. Instruct flask to use the right application
|
||||
|
||||
export FLASK_APP=flaskr.factory:create_app()
|
||||
|
||||
4. initialize the database with this command:
|
||||
|
||||
flask initdb
|
||||
|
||||
4. now you can run flaskr:
|
||||
5. now you can run flaskr:
|
||||
|
||||
flask run
|
||||
|
||||
|
|
@ -30,5 +36,5 @@
|
|||
|
||||
~ Is it tested?
|
||||
|
||||
You betcha. Run the `test_flaskr.py` file to see
|
||||
You betcha. Run `python setup.py test` to see
|
||||
the tests pass.
|
||||
|
|
|
|||
0
examples/flaskr/flaskr/__init__.py
Normal file
0
examples/flaskr/flaskr/__init__.py
Normal file
0
examples/flaskr/flaskr/blueprints/__init__.py
Normal file
0
examples/flaskr/flaskr/blueprints/__init__.py
Normal file
|
|
@ -10,29 +10,18 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
from sqlite3 import dbapi2 as sqlite3
|
||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||
render_template, flash
|
||||
from flask import Blueprint, request, session, g, redirect, url_for, abort, \
|
||||
render_template, flash, current_app
|
||||
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__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)
|
||||
# create our blueprint :)
|
||||
bp = Blueprint('flaskr', __name__)
|
||||
|
||||
|
||||
def connect_db():
|
||||
"""Connects to the specific database."""
|
||||
rv = sqlite3.connect(app.config['DATABASE'])
|
||||
rv = sqlite3.connect(current_app.config['DATABASE'])
|
||||
rv.row_factory = sqlite3.Row
|
||||
return rv
|
||||
|
||||
|
|
@ -40,18 +29,11 @@ def connect_db():
|
|||
def init_db():
|
||||
"""Initializes the database."""
|
||||
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.commit()
|
||||
|
||||
|
||||
@app.cli.command('initdb')
|
||||
def initdb_command():
|
||||
"""Creates the database tables."""
|
||||
init_db()
|
||||
print('Initialized the database.')
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Opens a new database connection if there is none yet for the
|
||||
current application context.
|
||||
|
|
@ -61,14 +43,7 @@ def get_db():
|
|||
return g.sqlite_db
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@bp.route('/')
|
||||
def show_entries():
|
||||
db = get_db()
|
||||
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)
|
||||
|
||||
|
||||
@app.route('/add', methods=['POST'])
|
||||
@bp.route('/add', methods=['POST'])
|
||||
def add_entry():
|
||||
if not session.get('logged_in'):
|
||||
abort(401)
|
||||
|
|
@ -85,26 +60,26 @@ def add_entry():
|
|||
[request.form['title'], request.form['text']])
|
||||
db.commit()
|
||||
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():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if request.form['username'] != app.config['USERNAME']:
|
||||
if request.form['username'] != current_app.config['USERNAME']:
|
||||
error = 'Invalid username'
|
||||
elif request.form['password'] != app.config['PASSWORD']:
|
||||
elif request.form['password'] != current_app.config['PASSWORD']:
|
||||
error = 'Invalid password'
|
||||
else:
|
||||
session['logged_in'] = True
|
||||
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)
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
@bp.route('/logout')
|
||||
def logout():
|
||||
session.pop('logged_in', None)
|
||||
flash('You were logged out')
|
||||
return redirect(url_for('show_entries'))
|
||||
return redirect(url_for('flaskr.show_entries'))
|
||||
64
examples/flaskr/flaskr/factory.py
Normal file
64
examples/flaskr/flaskr/factory.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Flaskr
|
||||
~~~~~~
|
||||
|
||||
A microblog example application written as Flask tutorial with
|
||||
Flask and sqlite3.
|
||||
|
||||
:copyright: (c) 2015 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
from flask import Flask, g
|
||||
from werkzeug.utils import find_modules, import_string
|
||||
from flaskr.blueprints.flaskr import init_db
|
||||
|
||||
|
||||
def create_app(config=None):
|
||||
app = Flask('flaskr')
|
||||
|
||||
app.config.update(dict(
|
||||
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
|
||||
DEBUG=True,
|
||||
SECRET_KEY='development key',
|
||||
USERNAME='admin',
|
||||
PASSWORD='default'
|
||||
))
|
||||
app.config.update(config or {})
|
||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||
|
||||
register_blueprints(app)
|
||||
register_cli(app)
|
||||
register_teardowns(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
"""Register all blueprint modules
|
||||
|
||||
Reference: Armin Ronacher, "Flask for Fun and for Profit" PyBay 2016.
|
||||
"""
|
||||
for name in find_modules('flaskr.blueprints'):
|
||||
mod = import_string(name)
|
||||
if hasattr(mod, 'bp'):
|
||||
app.register_blueprint(mod.bp)
|
||||
return None
|
||||
|
||||
|
||||
def register_cli(app):
|
||||
@app.cli.command('initdb')
|
||||
def initdb_command():
|
||||
"""Creates the database tables."""
|
||||
init_db()
|
||||
print('Initialized the database.')
|
||||
|
||||
|
||||
def register_teardowns(app):
|
||||
@app.teardown_appcontext
|
||||
def close_db(error):
|
||||
"""Closes the database again at the end of the request."""
|
||||
if hasattr(g, 'sqlite_db'):
|
||||
g.sqlite_db.close()
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
<h1>Flaskr</h1>
|
||||
<div class="metanav">
|
||||
{% if not session.logged_in %}
|
||||
<a href="{{ url_for('login') }}">log in</a>
|
||||
<a href="{{ url_for('flaskr.login') }}">log in</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('logout') }}">log out</a>
|
||||
<a href="{{ url_for('flaskr.logout') }}">log out</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% for message in get_flashed_messages() %}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
{% block body %}
|
||||
<h2>Login</h2>
|
||||
{% 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>
|
||||
<dt>Username:
|
||||
<dd><input type="text" name="username">
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% 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>
|
||||
<dt>Title:
|
||||
<dd><input type="text" size="30" name="title">
|
||||
|
|
@ -13,9 +13,9 @@
|
|||
{% endif %}
|
||||
<ul class="entries">
|
||||
{% for entry in entries %}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
|
||||
{% else %}
|
||||
<li><em>Unbelievable. No entries here so far</em>
|
||||
<li><em>Unbelievable. No entries here so far</em></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
2
examples/flaskr/setup.cfg
Normal file
2
examples/flaskr/setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[aliases]
|
||||
test=pytest
|
||||
27
examples/flaskr/setup.py
Normal file
27
examples/flaskr/setup.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# -*- 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(
|
||||
name='flaskr',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'flask',
|
||||
],
|
||||
setup_requires=[
|
||||
'pytest-runner',
|
||||
],
|
||||
tests_require=[
|
||||
'pytest',
|
||||
],
|
||||
)
|
||||
|
|
@ -9,24 +9,38 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
import os
|
||||
import flaskr
|
||||
import tempfile
|
||||
import pytest
|
||||
from flaskr.factory import create_app
|
||||
from flaskr.blueprints.flaskr import init_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(request):
|
||||
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
flaskr.app.config['TESTING'] = True
|
||||
client = flaskr.app.test_client()
|
||||
with flaskr.app.app_context():
|
||||
flaskr.init_db()
|
||||
def app(request):
|
||||
|
||||
db_fd, temp_db_location = tempfile.mkstemp()
|
||||
config = {
|
||||
'DATABASE': temp_db_location,
|
||||
'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():
|
||||
os.close(db_fd)
|
||||
os.unlink(flaskr.app.config['DATABASE'])
|
||||
os.close(app.config['DB_FD'])
|
||||
os.unlink(app.config['DATABASE'])
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
return client
|
||||
|
|
@ -49,25 +63,25 @@ def test_empty_db(client):
|
|||
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"""
|
||||
rv = login(client, flaskr.app.config['USERNAME'],
|
||||
flaskr.app.config['PASSWORD'])
|
||||
rv = login(client, app.config['USERNAME'],
|
||||
app.config['PASSWORD'])
|
||||
assert b'You were logged in' in rv.data
|
||||
rv = logout(client)
|
||||
assert b'You were logged out' in rv.data
|
||||
rv = login(client, flaskr.app.config['USERNAME'] + 'x',
|
||||
flaskr.app.config['PASSWORD'])
|
||||
rv = login(client,app.config['USERNAME'] + 'x',
|
||||
app.config['PASSWORD'])
|
||||
assert b'Invalid username' in rv.data
|
||||
rv = login(client, flaskr.app.config['USERNAME'],
|
||||
flaskr.app.config['PASSWORD'] + 'x')
|
||||
rv = login(client, app.config['USERNAME'],
|
||||
app.config['PASSWORD'] + 'x')
|
||||
assert b'Invalid password' in rv.data
|
||||
|
||||
|
||||
def test_messages(client):
|
||||
def test_messages(client, app):
|
||||
"""Test that messages work"""
|
||||
login(client, flaskr.app.config['USERNAME'],
|
||||
flaskr.app.config['PASSWORD'])
|
||||
login(client, app.config['USERNAME'],
|
||||
app.config['PASSWORD'])
|
||||
rv = client.post('/add', data=dict(
|
||||
title='<Hello>',
|
||||
text='<strong>HTML</strong> allowed here'
|
||||
2
examples/minitwit/.gitignore
vendored
Normal file
2
examples/minitwit/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
minitwit.db
|
||||
.eggs/
|
||||
3
examples/minitwit/MANIFEST.in
Normal file
3
examples/minitwit/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
graft minitwit/templates
|
||||
graft minitwit/static
|
||||
include minitwit/schema.sql
|
||||
|
|
@ -14,15 +14,19 @@
|
|||
export an MINITWIT_SETTINGS environment variable
|
||||
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
|
||||
|
||||
2. fire up a shell and run this:
|
||||
4. fire up a shell and run this:
|
||||
|
||||
flask initdb
|
||||
|
||||
3. now you can run minitwit:
|
||||
5. now you can run minitwit:
|
||||
|
||||
flask run
|
||||
|
||||
|
|
@ -31,5 +35,5 @@
|
|||
|
||||
~ 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.
|
||||
|
|
|
|||
1
examples/minitwit/minitwit/__init__.py
Normal file
1
examples/minitwit/minitwit/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .minitwit import app
|
||||
|
|
@ -25,7 +25,7 @@ DEBUG = True
|
|||
SECRET_KEY = 'development key'
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app = Flask('minitwit')
|
||||
app.config.from_object(__name__)
|
||||
app.config.from_envvar('MINITWIT_SETTINGS', silent=True)
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ def format_datetime(timestamp):
|
|||
|
||||
def gravatar_url(email, size=80):
|
||||
"""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)
|
||||
|
||||
|
||||
2
examples/minitwit/setup.cfg
Normal file
2
examples/minitwit/setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[aliases]
|
||||
test=pytest
|
||||
16
examples/minitwit/setup.py
Normal file
16
examples/minitwit/setup.py
Normal file
|
|
@ -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.
|
||||
"""
|
||||
import os
|
||||
import minitwit
|
||||
import tempfile
|
||||
import pytest
|
||||
from minitwit import minitwit
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(request):
|
||||
def client():
|
||||
db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
client = minitwit.app.test_client()
|
||||
with minitwit.app.app_context():
|
||||
minitwit.init_db()
|
||||
|
||||
def teardown():
|
||||
"""Get rid of the database again after each test."""
|
||||
os.close(db_fd)
|
||||
os.unlink(minitwit.app.config['DATABASE'])
|
||||
request.addfinalizer(teardown)
|
||||
return client
|
||||
yield client
|
||||
|
||||
os.close(db_fd)
|
||||
os.unlink(minitwit.app.config['DATABASE'])
|
||||
|
||||
|
||||
def register(client, username, password, password2=None, email=None):
|
||||
10
examples/patterns/largerapp/setup.py
Normal file
10
examples/patterns/largerapp/setup.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='yourapplication',
|
||||
packages=['yourapplication'],
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'flask',
|
||||
],
|
||||
)
|
||||
12
examples/patterns/largerapp/tests/test_largerapp.py
Normal file
12
examples/patterns/largerapp/tests/test_largerapp.py
Normal file
|
|
@ -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
|
||||
4
examples/patterns/largerapp/yourapplication/__init__.py
Normal file
4
examples/patterns/largerapp/yourapplication/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from flask import Flask
|
||||
app = Flask('yourapplication')
|
||||
|
||||
import yourapplication.views
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue