Browse Source

Merge branch 'master' into master

pull/1452/head
Kenneth Reitz 8 years ago committed by GitHub
parent
commit
d911c897ee
  1. 11
      .coveragerc
  2. 89
      .travis.yml
  3. 1
      AUTHORS
  4. 63
      CHANGES
  5. 53
      CONTRIBUTING.rst
  6. 4
      Makefile
  7. BIN
      docs/_static/pycharm-runconfig.png
  8. 2
      docs/_templates/sidebarintro.html
  9. 18
      docs/blueprints.rst
  10. 55
      docs/cli.rst
  11. 10
      docs/conf.py
  12. 40
      docs/config.rst
  13. 2
      docs/deploying/index.rst
  14. 16
      docs/deploying/wsgi-standalone.rst
  15. 32
      docs/extensiondev.rst
  16. 230
      docs/installation.rst
  17. 80
      docs/patterns/celery.rst
  18. 82
      docs/patterns/deferredcallbacks.rst
  19. 2
      docs/patterns/distribute.rst
  20. 54
      docs/patterns/errorpages.rst
  21. 8
      docs/patterns/fileuploads.rst
  22. 2
      docs/patterns/packages.rst
  23. 264
      docs/quickstart.rst
  24. 13
      docs/reqcontext.rst
  25. 4
      docs/signals.rst
  26. 2
      docs/styleguide.rst
  27. 202
      docs/testing.rst
  28. 3
      docs/tutorial/dbcon.rst
  29. 31
      docs/tutorial/dbinit.rst
  30. 14
      docs/tutorial/folders.rst
  31. 18
      docs/tutorial/index.rst
  32. 2
      docs/tutorial/introduction.rst
  33. 30
      docs/tutorial/packaging.rst
  34. 66
      docs/tutorial/setup.rst
  35. 7
      docs/tutorial/templates.rst
  36. 3
      docs/tutorial/views.rst
  37. 4
      docs/upgrading.rst
  38. 12
      examples/flaskr/tests/test_flaskr.py
  39. 12
      examples/minitwit/tests/test_minitwit.py
  40. 2
      flask/_compat.py
  41. 313
      flask/app.py
  42. 7
      flask/blueprints.py
  43. 131
      flask/cli.py
  44. 82
      flask/helpers.py
  45. 27
      flask/json.py
  46. 175
      flask/sessions.py
  47. 44
      flask/views.py
  48. 2
      scripts/flask-07-upgrade.py
  49. 3
      setup.cfg
  50. 4
      setup.py
  51. 2
      test-requirements.txt
  52. 71
      tests/conftest.py
  53. 96
      tests/test_appctx.py
  54. 1026
      tests/test_basic.py
  55. 464
      tests/test_blueprints.py
  56. 191
      tests/test_cli.py
  57. 15
      tests/test_deprecations.py
  58. 22
      tests/test_ext.py
  59. 717
      tests/test_helpers.py
  60. 11
      tests/test_regression.py
  61. 140
      tests/test_reqctx.py
  62. 12
      tests/test_signals.py
  63. 214
      tests/test_templating.py
  64. 176
      tests/test_testing.py
  65. 79
      tests/test_user_error_handler.py
  66. 127
      tests/test_views.py
  67. 74
      tox.ini

11
.coveragerc

@ -0,0 +1,11 @@
[run]
branch = True
source =
flask
tests
[paths]
source =
flask
.tox/*/lib/python*/site-packages/flask
.tox/pypy/site-packages/flask

89
.travis.yml

@ -1,52 +1,61 @@
sudo: false sudo: false
language: python language: python
python:
- "2.6"
- "2.7"
- "pypy"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
env:
- REQUIREMENTS=lowest
- REQUIREMENTS=lowest-simplejson
- REQUIREMENTS=release
- REQUIREMENTS=release-simplejson
- REQUIREMENTS=devel
- REQUIREMENTS=devel-simplejson
matrix: matrix:
exclude: include:
# Python 3 support currently does not work with lowest requirements - python: 3.6
- python: "3.3" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest - python: 3.5
- python: "3.3" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest-simplejson - python: 3.4
- python: "3.4" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest - python: 3.3
- python: "3.4" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest-simplejson - python: 2.7
- python: "3.5" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest - python: 2.6
- python: "3.5" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest-simplejson - python: pypy
- python: "3.6" env: TOXENV=py-release,codecov
env: REQUIREMENTS=lowest - python: nightly
- python: "3.6" env: TOXENV=py-release
env: REQUIREMENTS=lowest-simplejson - python: 3.6
env: TOXENV=docs-html
- python: 3.6
env: TOXENV=py-release-simplejson,codecov
- python: 2.7
env: TOXENV=py-release-simplejson,codecov
- python: pypy
env: TOXENV=py-release-simplejson,codecov
- python: 3.6
env: TOXENV=py-devel,codecov
- python: 3.3
env: TOXENV=py-devel,codecov
- python: 2.7
env: TOXENV=py-devel,codecov
- python: 2.6
env: TOXENV=py-devel,codecov
- python: pypy
env: TOXENV=py-devel,codecov
- python: 3.6
env: TOXENV=py-lowest,codecov
- python: 3.3
env: TOXENV=py-lowest,codecov
- python: 2.7
env: TOXENV=py-lowest,codecov
- python: 2.6
env: TOXENV=py-lowest,codecov
- python: pypy
env: TOXENV=py-lowest,codecov
install: install:
- pip install tox - pip install tox
script: script:
- tox -e py-$REQUIREMENTS - tox
branches: cache:
except: - pip
- website
notifications: notifications:
email: false email: false

1
AUTHORS

@ -21,6 +21,7 @@ Patches and Suggestions
- Florent Xicluna - Florent Xicluna
- Georg Brandl - Georg Brandl
- Jeff Widman @jeffwidman - Jeff Widman @jeffwidman
- Joshua Bronson @jab
- Justin Quick - Justin Quick
- Kenneth Reitz - Kenneth Reitz
- Keyan Pishdadian - Keyan Pishdadian

63
CHANGES

@ -11,18 +11,75 @@ Major release, unreleased
- Make `app.run()` into a noop if a Flask application is run from the - 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 development server on the command line. This avoids some behavior that
was confusing to debug for newcomers. was confusing to debug for newcomers.
- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify()
method returns compressed response by default, and pretty response in
debug mode.
- 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`_)
.. _#1489: https://github.com/pallets/flask/pull/1489
.. _#1898: https://github.com/pallets/flask/pull/1898
.. _#1936: https://github.com/pallets/flask/pull/1936
.. _#2017: https://github.com/pallets/flask/pull/2017
.. _#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
.. _#2297: https://github.com/pallets/flask/pull/2297
.. _#2319: https://github.com/pallets/flask/pull/2319
Version 0.12.2
--------------
Released on May 16 2017
- Fix a bug in `safe_join` on Windows.
Version 0.12.1 Version 0.12.1
-------------- --------------
Bugfix release, unreleased Bugfix release, released on March 31st 2017
- Prevent `flask run` from showing a NoAppException when an ImportError occurs - Prevent `flask run` from showing a NoAppException when an ImportError occurs
within the imported application module. within the imported application module.
- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix
``#2118``. ``#2118``.
- Use the``SERVER_NAME`` config if it is present as default values for - Use the ``SERVER_NAME`` config if it is present as default values for
``app.run``. ``#2109``, ``#2152`` ``app.run``. ``#2109``, ``#2152``
- Call `ctx.auto_pop` with the exception object instead of `None`, in the
event that a `BaseException` such as `KeyboardInterrupt` is raised in a
request handler.
Version 0.12 Version 0.12
------------ ------------
@ -127,6 +184,8 @@ Released on May 29th 2016, codename Absinthe.
- Don't leak exception info of already catched exceptions to context teardown - Don't leak exception info of already catched exceptions to context teardown
handlers (pull request ``#1393``). handlers (pull request ``#1393``).
- Allow custom Jinja environment subclasses (pull request ``#1422``). - Allow custom Jinja environment subclasses (pull request ``#1422``).
- Updated extension dev guidelines.
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
- Turn on autoescape for ``flask.templating.render_template_string`` by default - Turn on autoescape for ``flask.templating.render_template_string`` by default
(pull request ``#1515``). (pull request ``#1515``).

53
CONTRIBUTING.rst

@ -31,6 +31,42 @@ Submitting patches
- Try to follow `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_, but you - Try to follow `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_, but you
may ignore the line-length-limit if following it would make the code uglier. 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`_.
- Make sure you have a `GitHub account`_.
- Fork Flask to your GitHub account by clicking the `Fork`_ button.
- `Clone`_ your GitHub fork locally.
- Add the main repository as a remote to update later.
``git remote add pallets https://github.com/pallets/flask``
.. _GitHub account: https://github.com/join
.. _latest version of git: https://git-scm.com/downloads
.. _username: https://help.github.com/articles/setting-your-username-in-git/
.. _email: https://help.github.com/articles/setting-your-email-in-git/
.. _Fork: https://github.com/pallets/flask/pull/2305#fork-destination-box
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
Start coding
------------
- Create a branch to identify the issue you would like to work on (e.g.
``2287-dry-test-suite``)
- Using your favorite editor, make your changes, `committing as you go`_.
- Try to follow `PEP8`_, but you may ignore the line length limit if following
it would make the code uglier.
- Include tests that cover any code changes you make. Make sure the test fails
without your patch. `Run the tests. <contributing-testsuite_>`_.
- Push your commits to GitHub and `create a pull request`_.
- Celebrate 🎉
.. _committing as you go: http://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes
.. _PEP8: https://pep8.org/
.. _create a pull request: https://help.github.com/articles/creating-a-pull-request/
.. _contributing-testsuite:
Running the testsuite Running the testsuite
--------------------- ---------------------
@ -52,9 +88,19 @@ Install Flask as an editable package using the current source::
cd flask cd flask
pip install --editable . pip install --editable .
Running the testsuite
---------------------
The minimal requirement for running the testsuite is ``pytest``. You can
install it with::
pip install pytest
Then you can run the testsuite with:: Then you can run the testsuite with::
pytest pytest tests/
**Shortcut**: ``make test`` will ensure ``pytest`` is installed, and run it.
With only pytest installed, a large part of the testsuite will get skipped With only pytest installed, a large part of the testsuite will get skipped
though. Whether this is relevant depends on which part of Flask you're working though. Whether this is relevant depends on which part of Flask you're working
@ -69,6 +115,8 @@ of ``pytest``. You can install it with::
The ``tox`` command will then run all tests against multiple combinations The ``tox`` command will then run all tests against multiple combinations
Python versions and dependency versions. Python versions and dependency versions.
**Shortcut**: ``make tox-test`` will ensure ``tox`` is installed, and run it.
Running test coverage Running test coverage
--------------------- ---------------------
Generating a report of lines that do not have unit test coverage can indicate where Generating a report of lines that do not have unit test coverage can indicate where
@ -87,6 +135,9 @@ Generate a HTML report can be done using this command::
Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io
**Shortcut**: ``make cov`` will ensure ``pytest-cov`` is installed, run it, display the results, *and* save the HTML report.
Caution Caution
======= =======
pushing pushing

4
Makefile

@ -6,6 +6,10 @@ test:
pip install -r test-requirements.txt pip install -r test-requirements.txt
tox -e py-release tox -e py-release
cov:
pip install -r test-requirements.txt -q
FLASK_DEBUG= py.test --cov-report term --cov-report html --cov=flask --cov=examples tests examples
audit: audit:
python setup.py audit python setup.py audit

BIN
docs/_static/pycharm-runconfig.png vendored

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

2
docs/_templates/sidebarintro.html vendored

@ -1,6 +1,6 @@
<h3>About Flask</h3> <h3>About Flask</h3>
<p> <p>
Flask is a micro webdevelopment framework for Python. You are currently Flask is a micro web development framework for Python. You are currently
looking at the documentation of the development version. looking at the documentation of the development version.
</p> </p>
<h3>Other Formats</h3> <h3>Other Formats</h3>

18
docs/blueprints.rst

@ -245,4 +245,22 @@ Here is an example for a "404 Page Not Found" exception::
def page_not_found(e): def page_not_found(e):
return render_template('pages/404.html') return render_template('pages/404.html')
Most errorhandlers will simply work as expected; however, there is a caveat
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
access. This is because the blueprint does not "own" a certain URL space, so
the application instance has no way of knowing which blueprint errorhandler it
should run if given an invalid URL. If you would like to execute different
handling strategies for these errors based on URL prefixes, they may be defined
at the application level using the ``request`` proxy object::
@app.errorhandler(404)
@app.errorhandler(405)
def _handle_api_error(ex):
if request.path.startswith('/api/'):
return jsonify_error(ex)
else:
return ex
More information on error handling see :ref:`errorpages`. More information on error handling see :ref:`errorpages`.

55
docs/cli.rst

@ -56,6 +56,18 @@ If you are constantly working with a virtualenv you can also put the
bottom of the file. That way every time you activate your virtualenv you bottom of the file. That way every time you activate your virtualenv you
automatically also activate the correct application name. automatically also activate the correct application name.
Edit the activate script for the shell you use. For example:
Unix Bash: ``venv/bin/activate``::
FLASK_APP=hello
export FLASK_APP
Windows CMD.exe: ``venv\Scripts\activate.bat``::
set "FLASK_APP=hello"
:END
Debug Flag Debug Flag
---------- ----------
@ -246,3 +258,46 @@ Inside :file:`mypackage/commands.py` you can then export a Click object::
Once that package is installed in the same virtualenv as Flask itself you Once that package is installed in the same virtualenv as Flask itself you
can run ``flask my-command`` to invoke your command. This is useful to can run ``flask my-command`` to invoke your command. This is useful to
provide extra functionality that Flask itself cannot ship. provide extra functionality that Flask itself cannot ship.
PyCharm Integration
-------------------
The new Flask CLI features aren’t yet fully integrated into the PyCharm IDE,
so we have to do a few tweaks to get them working smoothly.
In your PyCharm application, with your project open, click on *Run*
from the menu bar and go to *Edit Configurations*. You’ll be greeted by a
screen similar to this:
.. image:: _static/pycharm-runconfig.png
:align: center
:class: screenshot
:alt: screenshot of pycharm's run configuration settings
There’s quite a few options to change, but don’t worry— once we’ve done it
for one command, we can easily copy the entire configuration and make a
single tweak to give us access to other flask cli commands, including
any custom ones you may implement yourself.
For the *Script* input (**A**), we want to navigate to the virtual environment
we’re using for our project and within that folder we want to pick the ``flask``
file which will reside in the ``bin`` folder, or in the ``Scripts`` folder if
you're on Windows.
The *Script Parameter* field (**B**) is set to the cli command you wish to
execute, in this example we use ``run`` which will run our development server.
We need to add an environment variable (**C**) to identify our application.
Click on the browse button and add an entry with ``FLASK_APP`` on the
left and the name of the python file, or package on the right
(``app.py`` for example).
Next we need to set the working directory (**D**) to be the same folder where
our application file or package resides.
Finally, untick the *PYTHONPATH* options (**E**) and give the configuration a
good descriptive name, such as “Run Flask Server” and click *Apply*.
Now that we have on run configuration which implements ``flask run`` from within
PyCharm, we can simply copy that configuration and alter the script argument
to run a different cli command, e.g. ``flask shell``.

10
docs/conf.py

@ -38,6 +38,14 @@ extensions = [
'flaskdocext' 'flaskdocext'
] ]
try:
__import__('sphinxcontrib.log_cabinet')
except ImportError:
print('sphinxcontrib-log-cabinet is not installed.')
print('Changelog directives will not be re-organized.')
else:
extensions.append('sphinxcontrib.log_cabinet')
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
@ -234,7 +242,7 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
# The scheme of the identifier. Typical schemes are ISBN or URL. # The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = '' #epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number # The unique identifier of the text. This can be an ISBN number
# or the project homepage. # or the project homepage.
#epub_identifier = '' #epub_identifier = ''

40
docs/config.rst

@ -44,6 +44,21 @@ method::
SECRET_KEY='...' SECRET_KEY='...'
) )
.. admonition:: Debug Mode with the ``flask`` Script
If you use the :command:`flask` script to start a local development
server, to enable the debug mode, you need to export the ``FLASK_DEBUG``
environment variable before running the server::
$ export FLASK_DEBUG=1
$ flask run
(On Windows you need to use ``set`` instead of ``export``).
``app.debug`` and ``app.config['DEBUG']`` are not compatible with
  the :command:`flask` script. They only worked when using ``Flask.run()``
method.
Builtin Configuration Values Builtin Configuration Values
---------------------------- ----------------------------
@ -52,7 +67,8 @@ The following configuration values are used internally by Flask:
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}| .. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
================================= ========================================= ================================= =========================================
``DEBUG`` enable/disable debug mode ``DEBUG`` enable/disable debug mode when using
``Flask.run()`` method to start server
``TESTING`` enable/disable testing mode ``TESTING`` enable/disable testing mode
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the ``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
propagation of exceptions. If not set or propagation of exceptions. If not set or
@ -116,13 +132,13 @@ The following configuration values are used internally by Flask:
by default enables URL generation by default enables URL generation
without a request context but with an without a request context but with an
application context. application context.
``APPLICATION_ROOT`` If the application does not occupy ``APPLICATION_ROOT`` The path value used for the session
a whole domain or subdomain this can cookie if ``SESSION_COOKIE_PATH`` isn't
be set to the path where the application set. If it's also ``None`` ``'/'`` is used.
is configured to live. This is for Note that to actually serve your Flask
session cookie as path value. If app under a subpath you need to tell
domains are used, this should be your WSGI container the ``SCRIPT_NAME``
``None``. WSGI environment variable.
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will ``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
reject incoming requests with a reject incoming requests with a
content length greater than this by content length greater than this by
@ -178,11 +194,9 @@ The following configuration values are used internally by Flask:
This is not recommended but might give This is not recommended but might give
you a performance improvement on the you a performance improvement on the
cost of cacheability. cost of cacheability.
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) ``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` or the Flask app
jsonify responses will be pretty printed is running in debug mode, jsonify responses
if they are not requested by an will be pretty printed.
XMLHttpRequest object (controlled by
the ``X-Requested-With`` header)
``JSONIFY_MIMETYPE`` MIME type used for jsonify responses. ``JSONIFY_MIMETYPE`` MIME type used for jsonify responses.
``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of ``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of
the template source and reload it the template source and reload it

2
docs/deploying/index.rst

@ -32,8 +32,8 @@ Self-hosted options
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
mod_wsgi
wsgi-standalone wsgi-standalone
uwsgi uwsgi
mod_wsgi
fastcgi fastcgi
cgi cgi

16
docs/deploying/wsgi-standalone.rst

@ -27,6 +27,22 @@ For example, to run a Flask application with 4 worker processes (``-w
.. _eventlet: http://eventlet.net/ .. _eventlet: http://eventlet.net/
.. _greenlet: https://greenlet.readthedocs.io/en/latest/ .. _greenlet: https://greenlet.readthedocs.io/en/latest/
uWSGI
--------
`uWSGI`_ is a fast application server written in C. It is very configurable
which makes it more complicated to setup than gunicorn.
Running `uWSGI HTTP Router`_::
uwsgi --http 127.0.0.1:5000 --module myproject:app
For a more optimized setup, see `configuring uWSGI and NGINX`_.
.. _uWSGI: http://uwsgi-docs.readthedocs.io/en/latest/
.. _uWSGI HTTP Router: http://uwsgi-docs.readthedocs.io/en/latest/HTTP.html#the-uwsgi-http-https-router
.. _configuring uWSGI and NGINX: uwsgi.html#starting-your-app-with-uwsgi
Gevent Gevent
------- -------

32
docs/extensiondev.rst

@ -42,7 +42,7 @@ that people can easily install the development version into their
virtualenv without having to download the library by hand. virtualenv without having to download the library by hand.
Flask extensions must be licensed under a BSD, MIT or more liberal license Flask extensions must be licensed under a BSD, MIT or more liberal license
to be able to be enlisted in the Flask Extension Registry. Keep in mind in order to be listed in the Flask Extension Registry. Keep in mind
that the Flask Extension Registry is a moderated place and libraries will that the Flask Extension Registry is a moderated place and libraries will
be reviewed upfront if they behave as required. be reviewed upfront if they behave as required.
@ -148,10 +148,10 @@ What to use depends on what you have in mind. For the SQLite 3 extension
we will use the class-based approach because it will provide users with an we will use the class-based approach because it will provide users with an
object that handles opening and closing database connections. object that handles opening and closing database connections.
What's important about classes is that they encourage to be shared around When designing your classes, it's important to make them easily reusable
on module level. In that case, the object itself must not under any at the module level. This means the object itself must not under any
circumstances store any application specific state and must be shareable circumstances store any application specific state and must be shareable
between different application. between different applications.
The Extension Code The Extension Code
------------------ ------------------
@ -328,10 +328,10 @@ development. If you want to learn more, it's a very good idea to check
out existing extensions on the `Flask Extension Registry`_. If you feel out existing extensions on the `Flask Extension Registry`_. If you feel
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
ideas for nice looking APIs. Especially if you do something nobody before ideas for nice looking APIs. Especially if you do something nobody before
you did, it might be a very good idea to get some more input. This not you did, it might be a very good idea to get some more input. This not only
only to get an idea about what people might want to have from an generates useful feedback on what people might want from an extension, but
extension, but also to avoid having multiple developers working on pretty also avoids having multiple developers working in isolation on pretty much the
much the same side by side. same problem.
Remember: good API design is hard, so introduce your project on the Remember: good API design is hard, so introduce your project on the
mailinglist, and let other developers give you a helping hand with mailinglist, and let other developers give you a helping hand with
@ -364,10 +364,10 @@ extension to be approved you have to follow these guidelines:
3. APIs of approved extensions will be checked for the following 3. APIs of approved extensions will be checked for the following
characteristics: characteristics:
- an approved extension has to support multiple applications - an approved extension has to support multiple applications
running in the same Python process. running in the same Python process.
- it must be possible to use the factory pattern for creating - it must be possible to use the factory pattern for creating
applications. applications.
4. The license must be BSD/MIT/WTFPL licensed. 4. The license must be BSD/MIT/WTFPL licensed.
5. The naming scheme for official extensions is *Flask-ExtensionName* or 5. The naming scheme for official extensions is *Flask-ExtensionName* or
@ -381,10 +381,10 @@ extension to be approved you have to follow these guidelines:
link to the documentation, website (if there is one) and there link to the documentation, website (if there is one) and there
must be a link to automatically install the development version must be a link to automatically install the development version
(``PackageName==dev``). (``PackageName==dev``).
9. The ``zip_safe`` flag in the setup script must be set to ``False``, 9. The ``zip_safe`` flag in the setup script must be set to ``False``,
even if the extension would be safe for zipping. even if the extension would be safe for zipping.
10. An extension currently has to support Python 2.6 as well as 10. An extension currently has to support Python 2.7, Python 3.3 and higher.
Python 2.7
Extension Import Transition Extension Import Transition

230
docs/installation.rst

@ -3,163 +3,173 @@
Installation Installation
============ ============
Flask depends on some external libraries, like `Werkzeug Python Version
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_. --------------
Werkzeug is a toolkit for WSGI, the standard Python interface between web
applications and a variety of servers for both development and deployment.
Jinja2 renders templates.
So how do you get all that on your computer quickly? There are many ways you We recommend using the latest version of Python 3. Flask supports Python 3.3
could do that, but the most kick-ass method is virtualenv, so let's have a look and newer, Python 2.6 and newer, and PyPy.
at that first.
You will need Python 2.6 or newer to get started, so be sure to have an Dependencies
up-to-date Python 2.x installation. For using Flask with Python 3 have a ------------
look at :ref:`python3-support`.
.. _virtualenv: These distributions will be installed automatically when installing Flask.
virtualenv * `Werkzeug`_ implements WSGI, the standard Python interface between
---------- applications and servers.
* `Jinja`_ is a template language that renders the pages your application
serves.
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
templates to avoid injection attacks.
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
to protect Flask's session cookie.
* `Click`_ is a framework for writing command line applications. It provides
the ``flask`` command and allows adding custom management commands.
Virtualenv is probably what you want to use during development, and if you have .. _Werkzeug: http://werkzeug.pocoo.org/
shell access to your production machines, you'll probably want to use it there, .. _Jinja: http://jinja.pocoo.org/
too. .. _MarkupSafe: https://pypi.python.org/pypi/MarkupSafe
.. _ItsDangerous: https://pythonhosted.org/itsdangerous/
.. _Click: http://click.pocoo.org/
What problem does virtualenv solve? If you like Python as much as I do, Optional dependencies
chances are you want to use it for other projects besides Flask-based web ~~~~~~~~~~~~~~~~~~~~~
applications. But the more projects you have, the more likely it is that you
will be working with different versions of Python itself, or at least different
versions of Python libraries. Let's face it: quite often libraries break
backwards compatibility, and it's unlikely that any serious application will
have zero dependencies. So what do you do if two or more of your projects have
conflicting dependencies?
Virtualenv to the rescue! Virtualenv enables multiple side-by-side These distributions will not be installed automatically. Flask will detect and
installations of Python, one for each project. It doesn't actually install use them if you install them.
separate copies of Python, but it does provide a clever way to keep different
project environments isolated. Let's see how virtualenv works.
If you are on Mac OS X or Linux, chances are that the following * `Blinker`_ provides support for :ref:`signals`.
command will work for you:: * `SimpleJSON`_ is a fast JSON implementation that is compatible with
Python's ``json`` module. It is preferred for JSON operations if it is
installed.
$ sudo pip install virtualenv .. _Blinker: https://pythonhosted.org/blinker/
.. _SimpleJSON: https://simplejson.readthedocs.io/
It will probably install virtualenv on your system. Maybe it's even Virtual environments
in your package manager. If you use Ubuntu, try:: --------------------
$ sudo apt-get install python-virtualenv Use a virtual environment to manage the dependencies for your project, both in
development and in production.
If you are on Windows and don't have the ``easy_install`` command, you must What problem does a virtual environment solve? The more Python projects you
install it first. Check the :ref:`windows-easy-install` section for more have, the more likely it is that you need to work with different versions of
information about how to do that. Once you have it installed, run the same Python libraries, or even Python itself. Newer versions of libraries for one
commands as above, but without the ``sudo`` prefix. project can break compatibility in another project.
Once you have virtualenv installed, just fire up a shell and create Virtual environments are independent groups of Python libraries, one for each
your own environment. I usually create a project folder and a :file:`venv` project. Packages installed for one project will not affect other projects or
folder within:: the operating system's packages.
$ mkdir myproject Python 3 comes bundled with the :mod:`venv` module to create virtual
$ cd myproject environments. If you're using a modern version of Python, you can continue on
$ virtualenv venv to the next section.
New python executable in venv/bin/python
Installing setuptools, pip............done.
Now, whenever you want to work on a project, you only have to activate the If you're using Python 2, see :ref:`install-install-virtualenv` first.
corresponding environment. On OS X and Linux, do the following::
$ . venv/bin/activate .. _install-create-env:
If you are a Windows user, the following command is for you:: Create an environment
~~~~~~~~~~~~~~~~~~~~~
$ venv\Scripts\activate Create a project folder and a :file:`venv` folder within:
Either way, you should now be using your virtualenv (notice how the prompt of .. code-block:: sh
your shell has changed to show the active environment).
And if you want to go back to the real world, use the following command:: mkdir myproject
cd myproject
python3 -m venv venv
$ deactivate On Windows:
After doing this, the prompt of your shell should be as familiar as before. .. code-block:: bat
Now, let's move on. Enter the following command to get Flask activated in your py -3 -m venv venv
virtualenv::
$ pip install Flask If you needed to install virtualenv because you are on an older version of
Python, use the following command instead:
A few seconds later and you are good to go. .. code-block:: sh
virtualenv venv
System-Wide Installation On Windows:
------------------------
This is possible as well, though I do not recommend it. Just run .. code-block:: bat
``pip`` with root privileges::
$ sudo pip install Flask \Python27\Scripts\virtualenv.exe venv
(On Windows systems, run it in a command-prompt window with administrator Activate the environment
privileges, and leave out ``sudo``.) ~~~~~~~~~~~~~~~~~~~~~~~~
Before you work on your project, activate the corresponding environment:
Living on the Edge .. code-block:: sh
------------------
. venv/bin/activate
On Windows:
.. code-block:: bat
venv\Scripts\activate
Your shell prompt will change to show the name of the activated environment.
Install Flask
-------------
If you want to work with the latest version of Flask, there are two ways: you Within the activated environment, use the following command to install Flask:
can either let ``pip`` pull in the development version, or you can tell
it to operate on a git checkout. Either way, virtualenv is recommended.
Get the git checkout in a new virtualenv and run in development mode:: .. code-block:: sh
$ git clone https://github.com/pallets/flask.git pip install Flask
Initialized empty Git repository in ~/dev/flask/.git/
$ cd flask Living on the edge
$ virtualenv venv ~~~~~~~~~~~~~~~~~~
New python executable in venv/bin/python
Installing setuptools, pip............done. If you want to work with the latest Flask code before it's released, install or
$ . venv/bin/activate update the code from the master branch:
$ python setup.py develop
... .. code-block:: sh
Finished processing dependencies for Flask
pip install -U https://github.com/pallets/flask/archive/master.tar.gz
.. _install-install-virtualenv:
Install virtualenv
------------------
This will pull in the dependencies and activate the git head as the current If you are using Python 2, the venv module is not available. Instead,
version inside the virtualenv. Then all you have to do is run ``git pull install `virtualenv`_.
origin`` to update to the latest version.
.. _windows-easy-install: On Linux, virtualenv is provided by your package manager:
`pip` and `setuptools` on Windows .. code-block:: sh
---------------------------------
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools`` # Debian, Ubuntu
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial sudo apt-get install python-virtualenv
package you will need is pip - this will let you install
anything else (like virtualenv). Fortunately there is a "bootstrap script"
you can run to install.
If you don't currently have ``pip``, then `get-pip.py` will install it for you. # CentOS, Fedora
sudo yum install python-virtualenv
`get-pip.py`_ # Arch
sudo pacman -S python-virtualenv
It should be double-clickable once you download it. If you already have ``pip``, If you are on Mac OS X or Windows, download `get-pip.py`_, then:
you can upgrade them by running::
> pip install --upgrade pip setuptools .. code-block:: sh
Most often, once you pull up a command prompt you want to be able to type ``pip`` sudo python2 Downloads/get-pip.py
and ``python`` which will run those things, but this might not automatically happen sudo python2 -m pip install virtualenv
on Windows, because it doesn't know where those executables are (give either a try!).
To fix this, you should be able to navigate to your Python install directory On Windows, as an administrator:
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
check that you can now just type ``python`` to bring up the interpreter.
Finally, to install `virtualenv`_, you can simply run:: .. code-block:: bat
> pip install virtualenv \Python27\python.exe Downloads\get-pip.py
\Python27\python.exe -m 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 .. _get-pip.py: https://bootstrap.pypa.io/get-pip.py

80
docs/patterns/celery.rst

@ -1,24 +1,27 @@
Celery Based Background Tasks Celery Background Tasks
============================= =======================
Celery is a task queue for Python with batteries included. It used to If your application has a long running task, such as processing some uploaded
have a Flask integration but it became unnecessary after some data or sending email, you don't want to wait for it to finish during a
restructuring of the internals of Celery with Version 3. This guide fills request. Instead, use a task queue to send the necessary data to another
in the blanks in how to properly use Celery with Flask but assumes that process that will run the task in the background while the request returns
you generally already read the `First Steps with Celery immediately.
<http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
guide in the official Celery documentation.
Installing Celery Celery is a powerful task queue that can be used for simple background tasks
----------------- as well as complex multi-stage programs and schedules. This guide will show you
how to configure Celery using Flask, but assumes you've already read the
`First Steps with Celery <http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html>`_
guide in the Celery documentation.
Celery is on the Python Package Index (PyPI), so it can be installed with Install
standard Python tools like :command:`pip` or :command:`easy_install`:: -------
Celery is a separate Python package. Install it from PyPI using pip::
$ pip install celery $ pip install celery
Configuring Celery Configure
------------------ ---------
The first thing you need is a Celery instance, this is called the celery The first thing you need is a Celery instance, this is called the celery
application. It serves the same purpose as the :class:`~flask.Flask` application. It serves the same purpose as the :class:`~flask.Flask`
@ -36,15 +39,18 @@ This is all that is necessary to properly integrate Celery with Flask::
from celery import Celery from celery import Celery
def make_celery(app): def make_celery(app):
celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'], celery = Celery(
broker=app.config['CELERY_BROKER_URL']) app.import_name,
backend=app.config['CELERY_RESULT_BACKEND'],
broker=app.config['CELERY_BROKER_URL']
)
celery.conf.update(app.config) celery.conf.update(app.config)
TaskBase = celery.Task
class ContextTask(TaskBase): class ContextTask(celery.Task):
abstract = True
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
with app.app_context(): with app.app_context():
return TaskBase.__call__(self, *args, **kwargs) return self.run(*args, **kwargs)
celery.Task = ContextTask celery.Task = ContextTask
return celery return celery
@ -53,11 +59,12 @@ from the application config, updates the rest of the Celery config from
the Flask config and then creates a subclass of the task that wraps the the Flask config and then creates a subclass of the task that wraps the
task execution in an application context. task execution in an application context.
Minimal Example An example task
--------------- ---------------
With what we have above this is the minimal example of using Celery with Let's write a task that adds two numbers together and returns the result. We
Flask:: configure Celery's broker and backend to use Redis, create a ``celery``
application using the factor from above, and then use it to define the task. ::
from flask import Flask from flask import Flask
@ -68,26 +75,27 @@ Flask::
) )
celery = make_celery(flask_app) celery = make_celery(flask_app)
@celery.task() @celery.task()
def add_together(a, b): def add_together(a, b):
return a + b return a + b
This task can now be called in the background: This task can now be called in the background::
>>> result = add_together.delay(23, 42) result = add_together.delay(23, 42)
>>> result.wait() result.wait() # 65
65
Running the Celery Worker Run a worker
------------------------- ------------
Now if you jumped in and already executed the above code you will be If you jumped in and already executed the above code you will be
disappointed to learn that your ``.wait()`` will never actually return. disappointed to learn that ``.wait()`` will never actually return.
That's because you also need to run celery. You can do that by running That's because you also need to run a Celery worker to receive and execute the
celery as a worker:: task. ::
$ celery -A your_application.celery worker $ celery -A your_application.celery worker
The ``your_application`` string has to point to your application's package The ``your_application`` string has to point to your application's package
or module that creates the `celery` object. or module that creates the ``celery`` object.
Now that the worker is running, ``wait`` will return the result once the task
is finished.

82
docs/patterns/deferredcallbacks.rst

@ -3,71 +3,43 @@
Deferred Request Callbacks Deferred Request Callbacks
========================== ==========================
One of the design principles of Flask is that response objects are created One of the design principles of Flask is that response objects are created and
and passed down a chain of potential callbacks that can modify them or passed down a chain of potential callbacks that can modify them or replace
replace them. When the request handling starts, there is no response them. When the request handling starts, there is no response object yet. It is
object yet. It is created as necessary either by a view function or by created as necessary either by a view function or by some other component in
some other component in the system. the system.
But what happens if you want to modify the response at a point where the What happens if you want to modify the response at a point where the response
response does not exist yet? A common example for that would be a does not exist yet? A common example for that would be a
before-request function that wants to set a cookie on the response object. :meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
response object.
One way is to avoid the situation. Very often that is possible. For
instance you can try to move that logic into an after-request callback One way is to avoid the situation. Very often that is possible. For instance
instead. Sometimes however moving that code there is just not a very you can try to move that logic into a :meth:`~flask.Flask.after_request`
pleasant experience or makes code look very awkward. 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. As an alternative, you can use :func:`~flask.after_this_request` to register
This way you can defer code execution from anywhere in the application. 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.
The Decorator
-------------
The following decorator is the key. It registers a function on a list on
the :data:`~flask.g` object::
from flask import g
def after_this_request(f):
if not hasattr(g, 'after_request_callbacks'):
g.after_request_callbacks = []
g.after_request_callbacks.append(f)
return f
Calling the Deferred
--------------------
Now you can use the `after_this_request` decorator to mark a function to
be called at the end of the request. But we still need to call them. For
this the following function needs to be registered as
:meth:`~flask.Flask.after_request` callback::
@app.after_request
def call_after_request_callbacks(response):
for callback in getattr(g, 'after_request_callbacks', ()):
callback(response)
return response
A Practical Example
-------------------
At any time during a request, we can register a function to be called at the At any time during a request, we can register a function to be called at the
end of the request. For example you can remember the current language of the end of the request. For example you can remember the current language of the
user in a cookie in the before-request function:: user in a cookie in a :meth:`~flask.Flask.before_request` callback::
from flask import request from flask import request, after_this_request
@app.before_request @app.before_request
def detect_user_language(): def detect_user_language():
language = request.cookies.get('user_lang') language = request.cookies.get('user_lang')
if language is None: if language is None:
language = guess_language_from_request() language = guess_language_from_request()
# when the response exists, set a cookie with the language
@after_this_request @after_this_request
def remember_language(response): def remember_language(response):
response.set_cookie('user_lang', language) response.set_cookie('user_lang', language)
g.language = language g.language = language

2
docs/patterns/distribute.rst

@ -174,4 +174,4 @@ the code without having to run ``install`` again after each change.
.. _pip: https://pypi.python.org/pypi/pip .. _pip: https://pypi.python.org/pypi/pip
.. _Setuptools: https://pythonhosted.org/setuptools .. _Setuptools: https://pypi.python.org/pypi/setuptools

54
docs/patterns/errorpages.rst

@ -47,37 +47,53 @@ even if the application behaves correctly:
Error Handlers Error Handlers
-------------- --------------
An error handler is a function, just like a view function, but it is An error handler is a function that returns a response when a type of error is
called when an error happens and is passed that error. The error is most raised, similar to how a view is a function that returns a response when a
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it request URL is matched. It is passed the instance of the error being handled,
can be a different error: a handler for internal server errors will be which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
passed other exception instances as well if they are uncaught. handler for "500 Internal Server Error" will be passed uncaught exceptions in
addition to explicit 500 errors.
An error handler is registered with the :meth:`~flask.Flask.errorhandler` An error handler is registered with the :meth:`~flask.Flask.errorhandler`
decorator and the error code of the exception. Keep in mind that Flask decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
will *not* set the error code for you, so make sure to also provide the can be registered for a status code, like 404, or for an exception class.
HTTP status code when returning a response.
Please note that if you add an error handler for "500 Internal Server The status code of the response will not be set to the handler's code. Make
Error", Flask will not trigger it if it's running in Debug mode. sure to provide the appropriate HTTP status code when returning a response from
a handler.
Here an example implementation for a "404 Page Not Found" exception:: A handler for "500 Internal Server Error" will not be used when running in
debug mode. Instead, the interactive debugger will be shown.
Here is an example implementation for a "404 Page Not Found" exception::
from flask import render_template from flask import render_template
@app.errorhandler(404) @app.errorhandler(404)
def page_not_found(e): def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404 return render_template('404.html'), 404
When using the :ref:`application factory pattern <app-factories>`::
from flask import Flask, render_template
def page_not_found(e):
return render_template('404.html'), 404
def create_app(config_filename):
app = Flask(__name__)
app.register_error_handler(404, page_not_found)
return app
An example template might be this: An example template might be this:
.. sourcecode:: html+jinja .. sourcecode:: html+jinja
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %} {% block title %}Page Not Found{% endblock %}
{% block body %} {% block body %}
<h1>Page Not Found</h1> <h1>Page Not Found</h1>
<p>What you were looking for is just not there. <p>What you were looking for is just not there.
<p><a href="{{ url_for('index') }}">go somewhere nice</a> <p><a href="{{ url_for('index') }}">go somewhere nice</a>
{% endblock %} {% endblock %}

8
docs/patterns/fileuploads.rst

@ -21,7 +21,7 @@ specific upload folder and displays a file to the user. Let's look at the
bootstrapping code for our application:: bootstrapping code for our application::
import os import os
from flask import Flask, request, redirect, url_for from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
UPLOAD_FOLDER = '/path/to/the/uploads' UPLOAD_FOLDER = '/path/to/the/uploads'
@ -58,7 +58,7 @@ the file and redirects the user to the URL for the uploaded file::
return redirect(request.url) return redirect(request.url)
file = request.files['file'] file = request.files['file']
# if user does not select file, browser also # if user does not select file, browser also
# submit a empty part without filename # submit an empty part without filename
if file.filename == '': if file.filename == '':
flash('No selected file') flash('No selected file')
return redirect(request.url) return redirect(request.url)
@ -72,8 +72,8 @@ the file and redirects the user to the URL for the uploaded file::
<title>Upload new File</title> <title>Upload new File</title>
<h1>Upload new File</h1> <h1>Upload new File</h1>
<form method=post enctype=multipart/form-data> <form method=post enctype=multipart/form-data>
<p><input type=file name=file> <input type=file name=file>
<input type=submit value=Upload> <input type=submit value=Upload>
</form> </form>
''' '''

2
docs/patterns/packages.rst

@ -65,7 +65,7 @@ that tells Flask where to find the application instance::
export FLASK_APP=yourapplication export FLASK_APP=yourapplication
If you are outside of the project directory make sure to provide the exact If you are outside of the project directory make sure to provide the exact
path to your application directory. Similiarly you can turn on "debug path to your application directory. Similarly you can turn on "debug
mode" with this environment variable:: mode" with this environment variable::
export FLASK_DEBUG=true export FLASK_DEBUG=true

264
docs/quickstart.rst

@ -50,7 +50,14 @@ to tell your terminal the application to work with by exporting the
$ flask run $ flask run
* Running on http://127.0.0.1:5000/ * Running on http://127.0.0.1:5000/
If you are on Windows you need to use ``set`` instead of ``export``. If you are on Windows, the environment variable syntax depends on command line
interpreter. On Command Prompt::
C:\path\to\app>set FLASK_APP=hello.py
And on PowerShell::
PS C:\path\to\app> $env:FLASK_APP = "hello.py"
Alternatively you can use :command:`python -m flask`:: Alternatively you can use :command:`python -m flask`::
@ -153,20 +160,22 @@ Screenshot of the debugger in action:
:class: screenshot :class: screenshot
:alt: screenshot of debugger in action :alt: screenshot of debugger in action
More information on using the debugger can be found in the `Werkzeug
documentation`_.
.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/debug/#using-the-debugger
Have another debugger in mind? See :ref:`working-with-debuggers`. Have another debugger in mind? See :ref:`working-with-debuggers`.
Routing Routing
------- -------
Modern web applications have beautiful URLs. This helps people remember Modern web applications use meaningful URLs to help users. Users are more
the URLs, which is especially handy for applications that are used from likely to like a page and come back if the page uses a meaningful URL they can
mobile devices with slower network connections. If the user can directly remember and use to directly visit a page.
go to the desired page without having to hit the index page it is more
likely they will like the page and come back next time.
As you have seen above, the :meth:`~flask.Flask.route` decorator is used to Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
bind a function to a URL. Here are some basic examples::
@app.route('/') @app.route('/')
def index(): def index():
@ -176,16 +185,16 @@ bind a function to a URL. Here are some basic examples::
def hello(): def hello():
return 'Hello, World' return 'Hello, World'
But there is more to it! You can make certain parts of the URL dynamic and You can do more! You can make parts of the URL dynamic and attach multiple
attach multiple rules to a function. rules to a function.
Variable Rules Variable Rules
`````````````` ``````````````
To add variable parts to a URL you can mark these special sections as You can add variable sections to a URL by marking sections with
``<variable_name>``. Such a part is then passed as a keyword argument to your ``<variable_name>``. Your function then receives the ``<variable_name>``
function. Optionally a converter can be used by specifying a rule with as a keyword argument. Optionally, you can use a converter to specify the type
``<converter:variable_name>``. Here are some nice examples:: of the argument like ``<converter:variable_name>``. ::
@app.route('/user/<username>') @app.route('/user/<username>')
def show_user_profile(username): def show_user_profile(username):
@ -197,175 +206,122 @@ function. Optionally a converter can be used by specifying a rule with
# show the post with the given id, the id is an integer # show the post with the given id, the id is an integer
return 'Post %d' % post_id return 'Post %d' % post_id
The following converters exist: @app.route('/path/<path:subpath>')
def show_subpath(subpath):
=========== =============================================== # show the subpath after /path/
`string` accepts any text without a slash (the default) return 'Subpath %s' % subpath
`int` accepts integers
`float` like ``int`` but for floating point values
`path` like the default but also accepts slashes
`any` matches one of the items provided
`uuid` accepts UUID strings
=========== ===============================================
.. admonition:: Unique URLs / Redirection Behavior Converter types:
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 ``string`` (default) accepts any text without a slash
precedents laid down by Apache and earlier HTTP servers. ``int`` accepts positive integers
``float`` accepts positive floating point values
``path`` like ``string`` but also accepts slashes
``uuid`` accepts UUID strings
========== ==========================================
Take these two rules:: Unique URLs / Redirection Behavior
``````````````````````````````````
@app.route('/projects/') Take these two rules::
def projects():
return 'The project page'
@app.route('/about') @app.route('/projects/')
def about(): def projects():
return 'The about page' return 'The project page'
Though they look rather similar, they differ in their use of the trailing @app.route('/about')
slash in the URL *definition*. In the first case, the canonical URL for the def about():
``projects`` endpoint has a trailing slash. In that sense, it is similar to return 'The about page'
a folder on a filesystem. Accessing it without a trailing slash will cause
Flask to redirect to the canonical URL with the trailing slash.
In the second case, however, the URL is defined without a trailing slash, Though they look similar, they differ in their use of the trailing slash in
rather like the pathname of a file on UNIX-like systems. Accessing the URL the URL. In the first case, the canonical URL for the ``projects`` endpoint
with a trailing slash will produce a 404 "Not Found" error. 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.
This behavior allows relative URLs to continue working even if the trailing In the second case, however, the URL definition lacks a trailing slash,
slash is omitted, consistent with how Apache and other servers work. Also, like the pathname of a file on UNIX-like systems. Accessing the URL with a
the URLs will stay unique, which helps search engines avoid indexing the trailing slash produces a 404 “Not Found” error.
same page twice.
This behavior allows relative URLs to continue working even if the trailing
slash is omitted, consistent with how Apache and other servers work. Also,
the URLs will stay unique, which helps search engines avoid indexing the
same page twice.
.. _url-building: .. _url-building:
URL Building URL Building
```````````` ````````````
If it can match URLs, can Flask also generate them? Of course it can. To To build a URL to a specific function, use the :func:`~flask.url_for` function.
build a URL to a specific function you can use the :func:`~flask.url_for` It accepts the name of the function as its first argument and any number of
function. It accepts the name of the function as first argument and a number keyword arguments, each corresponding to a variable part of the URL rule.
of keyword arguments, each corresponding to the variable part of the URL rule. Unknown variable parts are appended to the URL as query parameters.
Unknown variable parts are appended to the URL as query parameters. Here are
some examples::
>>> 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`).
Why would you want to build URLs using the URL reversing function Why would you want to build URLs using the URL reversing function
:func:`~flask.url_for` instead of hard-coding them into your templates? :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 1. Reversing is often more descriptive than hard-coding the URLs.
importantly, it allows you to change URLs in one go, without having to 2. You can change your URLs in one go instead of needing to remember to
remember to change URLs all over the place. manually change hard-coded URLs.
2. URL building will handle escaping of special characters and Unicode 3. URL building handles escaping of special characters and Unicode data
data transparently for you, so you don't have to deal with them. transparently.
3. If your application is placed outside the URL root - say, in 4. If your application is placed outside the URL root, for example, in
``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
that properly for you. 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 Methods
```````````` ````````````
HTTP (the protocol web applications are speaking) knows different methods for Web applications use different HTTP methods when accessing URLs. You should
accessing URLs. By default, a route only answers to ``GET`` requests, but that familiarize yourself with the HTTP methods as you work with Flask. By default,
can be changed by providing the ``methods`` argument to the a route only answers to ``GET`` requests. You can use the ``methods`` argument
:meth:`~flask.Flask.route` decorator. Here are some examples:: of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
::
from flask import request
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if request.method == 'POST': if request.method == 'POST':
return do_the_login() do_the_login()
else: else:
return show_the_login_form() show_the_login_form()
If ``GET`` is present, ``HEAD`` will be added automatically for you. You If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
don't have to deal with that. It will also make sure that ``HEAD`` requests and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
are handled as the `HTTP RFC`_ (the document describing the HTTP ``OPTIONS`` is automatically implemented for you.
protocol) demands, so you can completely ignore that part of the HTTP
specification. Likewise, as of Flask 0.6, ``OPTIONS`` is implemented for you
automatically as well.
You have no idea what an HTTP method is? Worry not, here is a quick
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: https://www.ietf.org/rfc/rfc2068.txt .. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt

13
docs/reqcontext.rst

@ -119,8 +119,8 @@ understand what is actually happening. The new behavior is quite simple:
not executed yet or at all (for example in test environments sometimes not executed yet or at all (for example in test environments sometimes
you might want to not execute before-request callbacks). you might want to not execute before-request callbacks).
Now what happens on errors? In production mode if an exception is not Now what happens on errors? If you are not in debug mode and an exception is not
caught, the 500 internal server handler is called. In development mode caught, the 500 internal server handler is called. In debug mode
however the exception is not further processed and bubbles up to the WSGI however the exception is not further processed and bubbles up to the WSGI
server. That way things like the interactive debugger can provide helpful server. That way things like the interactive debugger can provide helpful
debug information. debug information.
@ -214,10 +214,11 @@ provide you with important information.
Starting with Flask 0.7 you have finer control over that behavior by Starting with Flask 0.7 you have finer control over that behavior by
setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By
default it's linked to the setting of ``DEBUG``. If the application is in default it's linked to the setting of ``DEBUG``. If the application is in
debug mode the context is preserved, in production mode it's not. debug mode the context is preserved. If debug mode is set to off, the context
is not preserved.
Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` in production mode Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` if debug mode is set to off
as it will cause your application to leak memory on exceptions. However as it will cause your application to leak memory on exceptions. However,
it can be useful during development to get the same error preserving it can be useful during development to get the same error preserving
behavior as in development mode when attempting to debug an error that behavior as debug mode when attempting to debug an error that
only occurs under production settings. only occurs under production settings.

4
docs/signals.rst

@ -27,7 +27,7 @@ executed in undefined order and do not modify any data.
The big advantage of signals over handlers is that you can safely The big advantage of signals over handlers is that you can safely
subscribe to them for just a split second. These temporary subscribe to them for just a split second. These temporary
subscriptions are helpful for unittesting for example. Say you want to subscriptions are helpful for unit testing for example. Say you want to
know what templates were rendered as part of a request: signals allow you know what templates were rendered as part of a request: signals allow you
to do exactly that. to do exactly that.
@ -45,7 +45,7 @@ signal. When you subscribe to a signal, be sure to also provide a sender
unless you really want to listen for signals from all applications. This is unless you really want to listen for signals from all applications. This is
especially true if you are developing an extension. especially true if you are developing an extension.
For example, here is a helper context manager that can be used in a unittest For example, here is a helper context manager that can be used in a unit test
to determine which templates were rendered and what variables were passed to determine which templates were rendered and what variables were passed
to the template:: to the template::

2
docs/styleguide.rst

@ -167,7 +167,7 @@ Docstring conventions:
""" """
Module header: Module header:
The module header consists of an utf-8 encoding declaration (if non The module header consists of a utf-8 encoding declaration (if non
ASCII letters are used, but it is recommended all the time) and a ASCII letters are used, but it is recommended all the time) and a
standard docstring:: standard docstring::

202
docs/testing.rst

@ -5,23 +5,30 @@ Testing Flask Applications
**Something that is untested is broken.** **Something that is untested is broken.**
The origin of this quote is unknown and while it is not entirely correct, it is also The origin of this quote is unknown and while it is not entirely correct, it
not far from the truth. Untested applications make it hard to is also not far from the truth. Untested applications make it hard to
improve existing code and developers of untested applications tend to improve existing code and developers of untested applications tend to
become pretty paranoid. If an application has automated tests, you can become pretty paranoid. If an application has automated tests, you can
safely make changes and instantly know if anything breaks. safely make changes and instantly know if anything breaks.
Flask provides a way to test your application by exposing the Werkzeug Flask provides a way to test your application by exposing the Werkzeug
test :class:`~werkzeug.test.Client` and handling the context locals for you. test :class:`~werkzeug.test.Client` and handling the context locals for you.
You can then use that with your favourite testing solution. In this documentation You can then use that with your favourite testing solution.
we will use the :mod:`unittest` package that comes pre-installed with Python.
In this documentation we will use the `pytest`_ package as the base
framework for our tests. You can install it with ``pip``, like so::
pip install pytest
.. _pytest:
https://pytest.org
The Application The Application
--------------- ---------------
First, we need an application to test; we will use the application from First, we need an application to test; we will use the application from
the :ref:`tutorial`. If you don't have that application yet, get the the :ref:`tutorial`. If you don't have that application yet, get the
sources from `the examples`_. source code from `the examples`_.
.. _the examples: .. _the examples:
https://github.com/pallets/flask/tree/master/examples/flaskr/ https://github.com/pallets/flask/tree/master/examples/flaskr/
@ -29,90 +36,91 @@ sources from `the examples`_.
The Testing Skeleton The Testing Skeleton
-------------------- --------------------
In order to test the application, we add a second module We begin by adding a tests directory under the application root. Then
(:file:`flaskr_tests.py`) and create a unittest skeleton there:: create a Python file to store our tests (:file:`test_flaskr.py`). When we
format the filename like ``test_*.py``, it will be auto-discoverable by
pytest.
Next, we create a `pytest fixture`_ called
:func:`client` that configures
the application for testing and initializes a new database.::
import os import os
import flaskr
import unittest
import tempfile import tempfile
class FlaskrTestCase(unittest.TestCase): import pytest
from flaskr import flaskr
@pytest.fixture
def client():
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
flaskr.app.config['TESTING'] = True
client = flaskr.app.test_client()
def setUp(self): with flaskr.app.app_context():
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() flaskr.init_db()
flaskr.app.config['TESTING'] = True
self.app = flaskr.app.test_client()
with flaskr.app.app_context():
flaskr.init_db()
def tearDown(self): yield client
os.close(self.db_fd)
os.unlink(flaskr.app.config['DATABASE'])
if __name__ == '__main__': os.close(db_fd)
unittest.main() os.unlink(flaskr.app.config['DATABASE'])
The code in the :meth:`~unittest.TestCase.setUp` method creates a new test This client fixture will be called by each individual test. It gives us a
client and initializes a new database. This function is called before simple interface to the application, where we can trigger test requests to the
each individual test function is run. To delete the database after the application. The client will also keep track of cookies for us.
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.
This test client will give us a simple interface to the application. We can During setup, the ``TESTING`` config flag is activated. What
trigger test requests to the application, and the client will also keep track this does is disable error catching during request handling, so that
of cookies for us. you get better error reports when performing test requests against the
application.
Because SQLite3 is filesystem-based we can easily use the tempfile module Because SQLite3 is filesystem-based, we can easily use the :mod:`tempfile` module
to create a temporary database and initialize it. The to create a temporary database and initialize it. The
:func:`~tempfile.mkstemp` function does two things for us: it returns a :func:`~tempfile.mkstemp` function does two things for us: it returns a
low-level file handle and a random file name, the latter we use as low-level file handle and a random file name, the latter we use as
database name. We just have to keep the `db_fd` around so that we can use database name. We just have to keep the `db_fd` around so that we can use
the :func:`os.close` function to close the file. the :func:`os.close` function to close the file.
To delete the database after the test, the fixture closes the file and removes
it from the filesystem.
If we now run the test suite, we should see the following output:: If we now run the test suite, we should see the following output::
$ python flaskr_tests.py $ pytest
---------------------------------------------------------------------- ================ test session starts ================
Ran 0 tests in 0.000s rootdir: ./flask/examples/flaskr, inifile: setup.cfg
collected 0 items
OK =========== no tests ran in 0.07 seconds ============
Even though it did not run any actual tests, we already know that our flaskr Even though it did not run any actual tests, we already know that our ``flaskr``
application is syntactically valid, otherwise the import would have died application is syntactically valid, otherwise the import would have died
with an exception. with an exception.
.. _pytest fixture:
https://docs.pytest.org/en/latest/fixture.html
The First Test The First Test
-------------- --------------
Now it's time to start testing the functionality of the application. Now it's time to start testing the functionality of the application.
Let's check that the application shows "No entries here so far" if we Let's check that the application shows "No entries here so far" if we
access the root of the application (``/``). To do this, we add a new access the root of the application (``/``). To do this, we add a new
test method to our class, like this:: test function to :file:`test_flaskr.py`, like this::
class FlaskrTestCase(unittest.TestCase): def test_empty_db(client):
"""Start with a blank database."""
def setUp(self): rv = client.get('/')
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() assert b'No entries here so far' in rv.data
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
Notice that our test functions begin with the word `test`; this allows Notice that our test functions begin with the word `test`; this allows
:mod:`unittest` to automatically identify the method as a test to run. `pytest`_ to automatically identify the function as a test to run.
By using `self.app.get` we can send an HTTP ``GET`` request to the application with By using ``client.get`` we can send an HTTP ``GET`` request to the application with
the given path. The return value will be a :class:`~flask.Flask.response_class` object. the given path. The return value will be a :class:`~flask.Flask.response_class` object.
We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
the return value (as string) from the application. In this case, we ensure that the return value (as string) from the application. In this case, we ensure that
@ -120,12 +128,15 @@ the return value (as string) from the application. In this case, we ensure that
Run it again and you should see one passing test:: Run it again and you should see one passing test::
$ python flaskr_tests.py $ pytest -v
.
---------------------------------------------------------------------- ================ test session starts ================
Ran 1 test in 0.034s rootdir: ./flask/examples/flaskr, inifile: setup.cfg
collected 1 items
OK tests/test_flaskr.py::test_empty_db PASSED
============= 1 passed in 0.10 seconds ==============
Logging In and Out Logging In and Out
------------------ ------------------
@ -136,39 +147,47 @@ of the application. To do this, we fire some requests to the login and logout
pages with the required form data (username and password). And because the pages with the required form data (username and password). And because the
login and logout pages redirect, we tell the client to `follow_redirects`. login and logout pages redirect, we tell the client to `follow_redirects`.
Add the following two methods to your `FlaskrTestCase` class:: Add the following two functions to your :file:`test_flaskr.py` file::
def login(client, username, password):
return client.post('/login', data=dict(
username=username,
password=password
), follow_redirects=True)
def login(self, username, password):
return self.app.post('/login', data=dict(
username=username,
password=password
), follow_redirects=True)
def logout(self): def logout(client):
return self.app.get('/logout', follow_redirects=True) return client.get('/logout', follow_redirects=True)
Now we can easily test that logging in and out works and that it fails with Now we can easily test that logging in and out works and that it fails with
invalid credentials. Add this new test to the class:: invalid credentials. Add this new test function::
def test_login_logout(self): def test_login_logout(client):
rv = self.login('admin', 'default') """Make sure login and logout works."""
assert b'You were logged in' in rv.data
rv = self.logout() rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
assert b'You were logged out' in rv.data assert b'You were logged in' in rv.data
rv = self.login('adminx', 'default')
assert b'Invalid username' in rv.data rv = logout(client)
rv = self.login('admin', 'defaultx') assert b'You were logged out' in rv.data
assert b'Invalid password' in rv.data
rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD'])
assert b'Invalid username' in rv.data
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x')
assert b'Invalid password' in rv.data
Test Adding Messages Test Adding Messages
-------------------- --------------------
We should also test that adding messages works. Add a new test method We should also test that adding messages works. Add a new test function
like this:: like this::
def test_messages(self): def test_messages(client):
self.login('admin', 'default') """Test that messages work."""
rv = self.app.post('/add', data=dict(
login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
rv = client.post('/add', data=dict(
title='<Hello>', title='<Hello>',
text='<strong>HTML</strong> allowed here' text='<strong>HTML</strong> allowed here'
), follow_redirects=True) ), follow_redirects=True)
@ -181,22 +200,25 @@ which is the intended behavior.
Running that should now give us three passing tests:: Running that should now give us three passing tests::
$ python flaskr_tests.py $ pytest -v
...
---------------------------------------------------------------------- ================ test session starts ================
Ran 3 tests in 0.332s rootdir: ./flask/examples/flaskr, inifile: setup.cfg
collected 3 items
OK tests/test_flaskr.py::test_empty_db PASSED
tests/test_flaskr.py::test_login_logout PASSED
tests/test_flaskr.py::test_messages PASSED
============= 3 passed in 0.23 seconds ==============
For more complex tests with headers and status codes, check out the For more complex tests with headers and status codes, check out the
`MiniTwit Example`_ from the sources which contains a larger test `MiniTwit Example`_ from the sources which contains a larger test
suite. suite.
.. _MiniTwit Example: .. _MiniTwit Example:
https://github.com/pallets/flask/tree/master/examples/minitwit/ https://github.com/pallets/flask/tree/master/examples/minitwit/
Other Testing Tricks Other Testing Tricks
-------------------- --------------------

3
docs/tutorial/dbcon.rst

@ -3,6 +3,9 @@
Step 4: Database Connections Step 4: Database Connections
---------------------------- ----------------------------
Let's continue building our code in the ``flaskr.py`` file.
(Scroll to the end of the page for more about project layout.)
You currently have a function for establishing a database connection with You currently have a function for establishing a database connection with
`connect_db`, but by itself, it is not particularly useful. Creating and `connect_db`, but by itself, it is not particularly useful. Creating and
closing database connections all the time is very inefficient, so you will closing database connections all the time is very inefficient, so you will

31
docs/tutorial/dbinit.rst

@ -9,31 +9,37 @@ systems need a schema that tells them how to store that information.
Before starting the server for the first time, it's important to create Before starting the server for the first time, it's important to create
that schema. that schema.
Such a schema can be created by piping the ``schema.sql`` file into the Such a schema could be created by piping the ``schema.sql`` file into the
`sqlite3` command as follows:: ``sqlite3`` command as follows::
sqlite3 /tmp/flaskr.db < schema.sql sqlite3 /tmp/flaskr.db < schema.sql
The downside of this is that it requires the ``sqlite3`` command to be However, the downside of this is that it requires the ``sqlite3`` command
installed, which is not necessarily the case on every system. This also to be installed, which is not necessarily the case on every system. This
requires that you provide the path to the database, which can introduce also requires that you provide the path to the database, which can introduce
errors. It's a good idea to add a function that initializes the database errors.
for you, to the application.
To do this, you can create a function and hook it into a :command:`flask` Instead of the ``sqlite3`` command above, it's a good idea to add a function
command that initializes the database. For now just take a look at the to our application that initializes the database for you. To do this, you
code segment below. A good place to add this function, and command, is can create a function and hook it into a :command:`flask` command that
just below the `connect_db` function in :file:`flaskr.py`:: initializes the database.
Take a look at the code segment below. A good place to add this function,
and command, is just below the ``connect_db`` function in :file:`flaskr.py`::
def init_db(): def init_db():
db = get_db() db = get_db()
with app.open_resource('schema.sql', mode='r') as f: with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read()) db.cursor().executescript(f.read())
db.commit() db.commit()
@app.cli.command('initdb') @app.cli.command('initdb')
def initdb_command(): def initdb_command():
"""Initializes the database.""" """Initializes the database."""
init_db() init_db()
print('Initialized the database.') print('Initialized the database.')
@ -59,7 +65,8 @@ On that cursor, there is a method to execute a complete script. Finally, you
only have to commit the changes. SQLite3 and other transactional only have to commit the changes. SQLite3 and other transactional
databases will not commit unless you explicitly tell it to. databases will not commit unless you explicitly tell it to.
Now, it is possible to create a database with the :command:`flask` script:: Now, in a terminal, from the application root directory :file:`flaskr/` it is
possible to create a database with the :command:`flask` script::
flask initdb flask initdb
Initialized the database. Initialized the database.

14
docs/tutorial/folders.rst

@ -3,8 +3,11 @@
Step 0: Creating The Folders Step 0: Creating The Folders
============================ ============================
Before getting started, you will need to create the folders needed for this It is recommended to install your Flask application within a virtualenv. Please
application:: read the :ref:`installation` section to set up your environment.
Now that you have installed Flask, you will need to create the folders required
for this tutorial. Your directory structure will look like this::
/flaskr /flaskr
/flaskr /flaskr
@ -13,9 +16,10 @@ application::
The application will be installed and run as Python package. This is the The application will be installed and run as Python package. This is the
recommended way to install and run Flask applications. You will see exactly recommended way to install and run Flask applications. You will see exactly
how to run ``flaskr`` later on in this tutorial. For now go ahead and create how to run ``flaskr`` later on in this tutorial.
the applications directory structure. In the next few steps you will be
creating the database schema as well as the main module. For now go ahead and create the applications directory structure. In the next
few steps you will be creating the database schema as well as the main module.
As a quick side note, the files inside of the :file:`static` folder are As a quick side note, the files inside of the :file:`static` folder are
available to users of the application via HTTP. This is the place where CSS and available to users of the application via HTTP. This is the place where CSS and

18
docs/tutorial/index.rst

@ -3,19 +3,19 @@
Tutorial Tutorial
======== ========
You want to develop an application with Python and Flask? Here you have Learn by example to develop an application with Python and Flask.
the chance to learn by example. In this tutorial, we will create a simple
microblogging application. It only supports one user that can create In this tutorial, we will create a simple blogging application. It only
text-only entries and there are no feeds or comments, but it still supports one user, only allows text entries, and has no feeds or comments.
features everything you need to get started. We will use Flask and SQLite
as a database (which comes out of the box with Python) so there is nothing While very simple, this example still features everything you need to get
else you need. started. In addition to Flask, we will use SQLite for the database, which is
built-in to Python, so there is nothing else you need.
If you want the full source code in advance or for comparison, check out If you want the full source code in advance or for comparison, check out
the `example source`_. the `example source`_.
.. _example source: .. _example source: https://github.com/pallets/flask/tree/master/examples/flaskr/
https://github.com/pallets/flask/tree/master/examples/flaskr/
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

2
docs/tutorial/introduction.rst

@ -22,7 +22,7 @@ connections in a more intelligent way, allowing you to target different
relational databases at once and more. You might also want to consider relational databases at once and more. You might also want to consider
one of the popular NoSQL databases if your data is more suited for those. one of the popular NoSQL databases if your data is more suited for those.
Here a screenshot of the final application: Here is a screenshot of the final application:
.. image:: ../_static/flaskr.png .. image:: ../_static/flaskr.png
:align: center :align: center

30
docs/tutorial/packaging.rst

@ -25,9 +25,7 @@ changes, your code structure should be::
setup.py setup.py
MANIFEST.in MANIFEST.in
The content of the ``setup.py`` file for ``flaskr`` is: Create the ``setup.py`` file for ``flaskr`` with the following content::
.. sourcecode:: python
from setuptools import setup from setuptools import setup
@ -43,53 +41,53 @@ The content of the ``setup.py`` file for ``flaskr`` is:
When using setuptools, it is also necessary to specify any special files When using setuptools, it is also necessary to specify any special files
that should be included in your package (in the :file:`MANIFEST.in`). that should be included in your package (in the :file:`MANIFEST.in`).
In this case, the static and templates directories need to be included, In this case, the static and templates directories need to be included,
as well as the schema. Create the :file:`MANIFEST.in` and add the as well as the schema.
following lines::
Create the :file:`MANIFEST.in` and add the following lines::
graft flaskr/templates graft flaskr/templates
graft flaskr/static graft flaskr/static
include flaskr/schema.sql include flaskr/schema.sql
To simplify locating the application, add the following import statement Next, to simplify locating the application, create the file,
into this file, :file:`flaskr/__init__.py`: :file:`flaskr/__init__.py` containing only the following import statement::
.. sourcecode:: python
from .flaskr import app from .flaskr import app
This import statement brings the application instance into the top-level This import statement brings the application instance into the top-level
of the application package. When it is time to run the application, the of the application package. When it is time to run the application, the
Flask development server needs the location of the app instance. This Flask development server needs the location of the app instance. This
import statement simplifies the location process. Without it the export import statement simplifies the location process. Without the above
statement a few steps below would need to be import statement, the export statement a few steps below would need to be
``export FLASK_APP=flaskr.flaskr``. ``export FLASK_APP=flaskr.flaskr``.
At this point you should be able to install the application. As usual, it At this point you should be able to install the application. As usual, it
is recommended to install your Flask application within a `virtualenv`_. is recommended to install your Flask application within a `virtualenv`_.
With that said, go ahead and install the application with:: With that said, from the ``flaskr/`` directory, go ahead and install the
application with::
pip install --editable . pip install --editable .
The above installation command assumes that it is run within the projects The above installation command assumes that it is run within the projects
root directory, `flaskr/`. The `editable` flag allows editing root directory, ``flaskr/``. The ``editable`` flag allows editing
source code without having to reinstall the Flask app each time you make 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 changes. The flaskr app is now installed in your virtualenv (see output
of ``pip freeze``). of ``pip freeze``).
With that out of the way, you should be able to start up the application. With that out of the way, you should be able to start up the application.
Do this with the following commands:: Do this on Mac or Linux with the following commands in ``flaskr/``::
export FLASK_APP=flaskr export FLASK_APP=flaskr
export FLASK_DEBUG=true export FLASK_DEBUG=true
flask run flask run
(In case you are on Windows you need to use `set` instead of `export`). (In case you are on Windows you need to use ``set`` instead of ``export``).
The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger. The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger.
*Never leave debug mode activated in a production system*, because it will *Never leave debug mode activated in a production system*, because it will
allow users to execute code on the server! allow users to execute code on the server!
You will see a message telling you that server has started along with You will see a message telling you that server has started along with
the address at which you can access it. the address at which you can access it in a browser.
When you head over to the server in your browser, you will get a 404 error When you head over to the server in your browser, you will get a 404 error
because we don't have any views yet. That will be addressed a little later, because we don't have any views yet. That will be addressed a little later,

66
docs/tutorial/setup.rst

@ -3,27 +3,31 @@
Step 2: Application Setup Code Step 2: Application Setup Code
============================== ==============================
Now that the schema is in place, you can create the application module, Next, we will create the application module, :file:`flaskr.py`. Just like the
:file:`flaskr.py`. This file should be placed inside of the :file:`schema.sql` file you created in the previous step, this file should be
:file:`flaskr/flaskr` folder. The first several lines of code in the placed inside of the :file:`flaskr/flaskr` folder.
application module are the needed import statements. After that there will be a
few lines of configuration code. For small applications like ``flaskr``, it is For this tutorial, all the Python code we use will be put into this file
possible to drop the configuration directly into the module. However, a cleaner (except for one line in ``__init__.py``, and any testing or optional files you
solution is to create a separate ``.ini`` or ``.py`` file, load that, and decide to create).
import the values from there.
The first several lines of code in the application module are the needed import
statements. After that there will be a few lines of configuration code.
For small applications like ``flaskr``, it is possible to drop the configuration
directly into the module. However, a cleaner solution is to create a separate
``.py`` file, load that, and import the values from there.
Here are the import statements (in :file:`flaskr.py`):: Here are the import statements (in :file:`flaskr.py`)::
# all the imports
import os import os
import sqlite3 import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, \
render_template, flash
The next couple lines will create the actual application instance and from flask import (Flask, request, session, g, redirect, url_for, abort,
initialize it with the config from the same file in :file:`flaskr.py`: render_template, flash)
.. sourcecode:: python 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 = Flask(__name__) # create the application instance :)
app.config.from_object(__name__) # load config from this file , flaskr.py app.config.from_object(__name__) # load config from this file , flaskr.py
@ -37,8 +41,8 @@ initialize it with the config from the same file in :file:`flaskr.py`:
)) ))
app.config.from_envvar('FLASKR_SETTINGS', silent=True) app.config.from_envvar('FLASKR_SETTINGS', silent=True)
The :class:`~flask.Config` object works similarly to a dictionary, so it can be In the above code, the :class:`~flask.Config` object works similarly to a
updated with new values. dictionary, so it can be updated with new values.
.. admonition:: Database Path .. admonition:: Database Path
@ -58,15 +62,15 @@ updated with new values.
Usually, it is a good idea to load a separate, environment-specific Usually, it is a good idea to load a separate, environment-specific
configuration file. Flask allows you to import multiple configurations and it configuration file. Flask allows you to import multiple configurations and it
will use the setting defined in the last import. This enables robust will use the setting defined in the last import. This enables robust
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this. configuration setups. :meth:`~flask.Config.from_envvar` can help achieve
this. ::
.. sourcecode:: python
app.config.from_envvar('FLASKR_SETTINGS', silent=True) app.config.from_envvar('FLASKR_SETTINGS', silent=True)
Simply define the environment variable :envvar:`FLASKR_SETTINGS` that points to If you want to do this (not required for this tutorial) simply define the
a config file to be loaded. The silent switch just tells Flask to not complain environment variable :envvar:`FLASKR_SETTINGS` that points to a config file
if no such environment key is set. to be loaded. The silent switch just tells Flask to not complain if no such
environment key is set.
In addition to that, you can use the :meth:`~flask.Config.from_object` In addition to that, you can use the :meth:`~flask.Config.from_object`
method on the config object and provide it with an import name of a method on the config object and provide it with an import name of a
@ -76,22 +80,22 @@ that in all cases, only variable names that are uppercase are considered.
The ``SECRET_KEY`` is needed to keep the client-side sessions secure. The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
Choose that key wisely and as hard to guess and complex as possible. Choose that key wisely and as hard to guess and complex as possible.
Lastly, you will add a method that allows for easy connections to the Lastly, add a method that allows for easy connections to the specified
specified database. This can be used to open a connection on request and database. ::
also from the interactive Python shell or a script. This will come in
handy later. You can create a simple database connection through SQLite and
then tell it to use the :class:`sqlite3.Row` object to represent rows.
This allows the rows to be treated as if they were dictionaries instead of
tuples.
.. sourcecode:: python
def connect_db(): def connect_db():
"""Connects to the specific database.""" """Connects to the specific database."""
rv = sqlite3.connect(app.config['DATABASE']) rv = sqlite3.connect(app.config['DATABASE'])
rv.row_factory = sqlite3.Row rv.row_factory = sqlite3.Row
return rv return rv
This can be used to open a connection on request and also from the
interactive Python shell or a script. This will come in handy later.
You can create a simple database connection through SQLite and then tell
it to use the :class:`sqlite3.Row` object to represent rows. This allows
the rows to be treated as if they were dictionaries instead of tuples.
In the next section you will see how to run the application. In the next section you will see how to run the application.
Continue with :ref:`tutorial-packaging`. Continue with :ref:`tutorial-packaging`.

7
docs/tutorial/templates.rst

@ -15,7 +15,8 @@ escaped with their XML equivalents.
We are also using template inheritance which makes it possible to reuse We are also using template inheritance which makes it possible to reuse
the layout of the website in all pages. the layout of the website in all pages.
Put the following templates into the :file:`templates` folder: Create the follwing three HTML files and place them in the
:file:`templates` folder:
.. _Jinja2: http://jinja.pocoo.org/docs/templates .. _Jinja2: http://jinja.pocoo.org/docs/templates
@ -79,9 +80,9 @@ HTTP method:
{% endif %} {% endif %}
<ul class=entries> <ul class=entries>
{% for entry in entries %} {% for entry in entries %}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }} <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
{% else %} {% else %}
<li><em>Unbelievable. No entries here so far</em> <li><em>Unbelievable. No entries here so far</em></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}

3
docs/tutorial/views.rst

@ -4,7 +4,8 @@ Step 6: The View Functions
========================== ==========================
Now that the database connections are working, you can start writing the Now that the database connections are working, you can start writing the
view functions. You will need four of them: view functions. You will need four of them; Show Entries, Add New Entry,
Login and Logout. Add the following code snipets to :file:`flaskr.py`.
Show Entries Show Entries
------------ ------------

4
docs/upgrading.rst

@ -49,7 +49,7 @@ Any of the following is functionally equivalent::
response = send_file(open(fname), attachment_filename=fname) response = send_file(open(fname), attachment_filename=fname)
response.set_etag(...) response.set_etag(...)
The reason for this is that some file-like objects have a invalid or even 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 misleading ``name`` attribute. Silently swallowing errors in such cases was not
a satisfying solution. a satisfying solution.
@ -198,7 +198,7 @@ applications with Flask. Because we want to make upgrading as easy as
possible we tried to counter the problems arising from these changes by possible we tried to counter the problems arising from these changes by
providing a script that can ease the transition. providing a script that can ease the transition.
The script scans your whole application and generates an unified diff with The script scans your whole application and generates a unified diff with
changes it assumes are safe to apply. However as this is an automated changes it assumes are safe to apply. However as this is an automated
tool it won't be able to find all use cases and it might miss some. We tool it won't be able to find all use cases and it might miss some. We
internally spread a lot of deprecation warnings all over the place to make internally spread a lot of deprecation warnings all over the place to make

12
examples/flaskr/tests/test_flaskr.py

@ -16,19 +16,15 @@ from flaskr import flaskr
@pytest.fixture @pytest.fixture
def client(request): def client():
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
flaskr.app.config['TESTING'] = True flaskr.app.config['TESTING'] = True
client = flaskr.app.test_client() client = flaskr.app.test_client()
with flaskr.app.app_context(): with flaskr.app.app_context():
flaskr.init_db() flaskr.init_db()
yield client
def teardown(): os.close(db_fd)
os.close(db_fd) os.unlink(flaskr.app.config['DATABASE'])
os.unlink(flaskr.app.config['DATABASE'])
request.addfinalizer(teardown)
return client
def login(client, username, password): def login(client, username, password):

12
examples/minitwit/tests/test_minitwit.py

@ -15,18 +15,16 @@ from minitwit import minitwit
@pytest.fixture @pytest.fixture
def client(request): def client():
db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp() db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp()
client = minitwit.app.test_client() client = minitwit.app.test_client()
with minitwit.app.app_context(): with minitwit.app.app_context():
minitwit.init_db() minitwit.init_db()
def teardown(): yield client
"""Get rid of the database again after each test."""
os.close(db_fd) os.close(db_fd)
os.unlink(minitwit.app.config['DATABASE']) os.unlink(minitwit.app.config['DATABASE'])
request.addfinalizer(teardown)
return client
def register(client, username, password, password2=None, email=None): def register(client, username, password, password2=None, email=None):

2
flask/_compat.py

@ -25,6 +25,7 @@ if not PY2:
itervalues = lambda d: iter(d.values()) itervalues = lambda d: iter(d.values())
iteritems = lambda d: iter(d.items()) iteritems = lambda d: iter(d.items())
from inspect import getfullargspec as getargspec
from io import StringIO from io import StringIO
def reraise(tp, value, tb=None): def reraise(tp, value, tb=None):
@ -43,6 +44,7 @@ else:
itervalues = lambda d: d.itervalues() itervalues = lambda d: d.itervalues()
iteritems = lambda d: d.iteritems() iteritems = lambda d: d.iteritems()
from inspect import getargspec
from cStringIO import StringIO from cStringIO import StringIO
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')

313
flask/app.py

@ -10,30 +10,30 @@
""" """
import os import os
import sys import sys
from threading import Lock
from datetime import timedelta from datetime import timedelta
from itertools import chain
from functools import update_wrapper from functools import update_wrapper
from itertools import chain
from threading import Lock
from werkzeug.datastructures import ImmutableDict from werkzeug.datastructures import ImmutableDict, Headers
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError from werkzeug.exceptions import BadRequest, HTTPException, \
from werkzeug.exceptions import HTTPException, InternalServerError, \ InternalServerError, MethodNotAllowed, default_exceptions
MethodNotAllowed, BadRequest, default_exceptions from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ from . import cli, json
locked_cached_property, _endpoint_from_view_func, find_package, \ from ._compat import integer_types, reraise, string_types, text_type
get_debug_flag from .config import Config, ConfigAttribute
from . import json, cli from .ctx import AppContext, RequestContext, _AppCtxGlobals
from .wrappers import Request, Response from .globals import _request_ctx_stack, g, request, session
from .config import ConfigAttribute, Config from .helpers import _PackageBoundObject, \
from .ctx import RequestContext, AppContext, _AppCtxGlobals _endpoint_from_view_func, find_package, get_debug_flag, \
from .globals import _request_ctx_stack, request, session, g get_flashed_messages, locked_cached_property, url_for
from .sessions import SecureCookieSessionInterface from .sessions import SecureCookieSessionInterface
from .signals import appcontext_tearing_down, got_request_exception, \
request_finished, request_started, request_tearing_down
from .templating import DispatchingJinjaLoader, Environment, \ from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor _default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception, \ from .wrappers import Request, Response
request_tearing_down, appcontext_tearing_down
from ._compat import reraise, string_types, text_type, integer_types
# a lock used for logger initialization # a lock used for logger initialization
_logger_lock = Lock() _logger_lock = Lock()
@ -123,6 +123,9 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.11 .. versionadded:: 0.11
The `root_path` parameter was added. The `root_path` parameter was added.
.. versionadded:: 0.13
The `host_matching` and `static_host` parameters were added.
:param import_name: the name of the application package :param import_name: the name of the application package
:param static_url_path: can be used to specify a different path for the :param static_url_path: can be used to specify a different path for the
static files on the web. Defaults to the name static files on the web. Defaults to the name
@ -130,6 +133,11 @@ class Flask(_PackageBoundObject):
:param static_folder: the folder with static files that should be served :param static_folder: the folder with static files that should be served
at `static_url_path`. Defaults to the ``'static'`` at `static_url_path`. Defaults to the ``'static'``
folder in the root path of the application. folder in the root path of the application.
:param host_matching: sets the app's ``url_map.host_matching`` to the given
given value. Defaults to False.
:param static_host: the host to use when adding the static route. Defaults
to None. Required when using ``host_matching=True``
with a ``static_folder`` configured.
:param template_folder: the folder that contains the templates that should :param template_folder: the folder that contains the templates that should
be used by the application. Defaults to be used by the application. Defaults to
``'templates'`` folder in the root path of the ``'templates'`` folder in the root path of the
@ -212,7 +220,7 @@ class Flask(_PackageBoundObject):
#: The testing flag. Set this to ``True`` to enable the test mode of #: The testing flag. Set this to ``True`` to enable the test mode of
#: Flask extensions (and in the future probably also Flask itself). #: Flask extensions (and in the future probably also Flask itself).
#: For example this might activate unittest helpers that have an #: For example this might activate test helpers that have an
#: additional runtime cost which should not be enabled by default. #: additional runtime cost which should not be enabled by default.
#: #:
#: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
@ -314,7 +322,7 @@ class Flask(_PackageBoundObject):
'PREFERRED_URL_SCHEME': 'http', 'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True, 'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True, 'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_PRETTYPRINT_REGULAR': False,
'JSONIFY_MIMETYPE': 'application/json', 'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None, 'TEMPLATES_AUTO_RELOAD': None,
}) })
@ -337,7 +345,8 @@ class Flask(_PackageBoundObject):
session_interface = SecureCookieSessionInterface() session_interface = SecureCookieSessionInterface()
def __init__(self, import_name, static_path=None, static_url_path=None, def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates', static_folder='static', static_host=None,
host_matching=False, template_folder='templates',
instance_path=None, instance_relative_config=False, instance_path=None, instance_relative_config=False,
root_path=None): root_path=None):
_PackageBoundObject.__init__(self, import_name, _PackageBoundObject.__init__(self, import_name,
@ -391,7 +400,7 @@ class Flask(_PackageBoundObject):
#: is the class for the instance check and the second the error handler #: is the class for the instance check and the second the error handler
#: function. #: function.
#: #:
#: To register a error handler, use the :meth:`errorhandler` #: To register an error handler, use the :meth:`errorhandler`
#: decorator. #: decorator.
self.error_handler_spec = {None: self._error_handlers} self.error_handler_spec = {None: self._error_handlers}
@ -404,17 +413,16 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.9 #: .. versionadded:: 0.9
self.url_build_error_handlers = [] self.url_build_error_handlers = []
#: A dictionary with lists of functions that should be called at the #: A dictionary with lists of functions that will be called at the
#: beginning of the request. The key of the dictionary is the name of #: beginning of each request. The key of the dictionary is the name of
#: the blueprint this function is active for, ``None`` for all requests. #: the blueprint this function is active for, or ``None`` for all
#: This can for example be used to open database connections or #: requests. To register a function, use the :meth:`before_request`
#: getting hold of the currently logged in user. To register a #: decorator.
#: function here, use the :meth:`before_request` decorator.
self.before_request_funcs = {} self.before_request_funcs = {}
#: A lists of functions that should be called at the beginning of the #: A list of functions that will be called at the beginning of the
#: first request to this instance. To register a function here, use #: first request to this instance. To register a function, use the
#: the :meth:`before_first_request` decorator. #: :meth:`before_first_request` decorator.
#: #:
#: .. versionadded:: 0.8 #: .. versionadded:: 0.8
self.before_first_request_funcs = [] self.before_first_request_funcs = []
@ -446,12 +454,11 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.9 #: .. versionadded:: 0.9
self.teardown_appcontext_funcs = [] self.teardown_appcontext_funcs = []
#: A dictionary with lists of functions that can be used as URL #: A dictionary with lists of functions that are called before the
#: value processor functions. Whenever a URL is built these functions #: :attr:`before_request_funcs` functions. The key of the dictionary is
#: are called to modify the dictionary of values in place. The key #: the name of the blueprint this function is active for, or ``None``
#: ``None`` here is used for application wide #: for all requests. To register a function, use
#: callbacks, otherwise the key is the name of the blueprint. #: :meth:`url_value_preprocessor`.
#: Each of these functions has the chance to modify the dictionary
#: #:
#: .. versionadded:: 0.7 #: .. versionadded:: 0.7
self.url_value_preprocessors = {} self.url_value_preprocessors = {}
@ -525,19 +532,22 @@ class Flask(_PackageBoundObject):
#: app.url_map.converters['list'] = ListConverter #: app.url_map.converters['list'] = ListConverter
self.url_map = Map() self.url_map = Map()
self.url_map.host_matching = host_matching
# tracks internally if the application already handled at least one # tracks internally if the application already handled at least one
# request. # request.
self._got_first_request = False self._got_first_request = False
self._before_request_lock = Lock() self._before_request_lock = Lock()
# register the static folder for the application. Do that even # Add a static route using the provided static_url_path, static_host,
# if the folder does not exist. First of all it might be created # and static_folder if there is a configured static_folder.
# while the server is running (usually happens during development) # Note we do this without checking if static_folder exists.
# but also because google appengine stores static files somewhere # For one, it might be created while the server is running (e.g. during
# else when mapped with the .yml file. # development). Also, Google App Engine stores static files somewhere
if self.has_static_folder: if self.has_static_folder:
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
self.add_url_rule(self.static_url_path + '/<path:filename>', self.add_url_rule(self.static_url_path + '/<path:filename>',
endpoint='static', endpoint='static', host=static_host,
view_func=self.send_static_file) view_func=self.send_static_file)
#: The click command line context for this application. Commands #: The click command line context for this application. Commands
@ -813,7 +823,8 @@ class Flask(_PackageBoundObject):
:param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
have the server available externally as well. Defaults to have the server available externally as well. Defaults to
``'127.0.0.1'``. ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config
variable if present.
:param port: the port of the webserver. Defaults to ``5000`` or the :param port: the port of the webserver. Defaults to ``5000`` or the
port defined in the ``SERVER_NAME`` config variable if port defined in the ``SERVER_NAME`` config variable if
present. present.
@ -965,7 +976,7 @@ class Flask(_PackageBoundObject):
return iter(self._blueprint_order) return iter(self._blueprint_order)
@setupmethod @setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options): def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
"""Connects a URL rule. Works exactly like the :meth:`route` """Connects a URL rule. Works exactly like the :meth:`route`
decorator. If a view_func is provided it will be registered with the decorator. If a view_func is provided it will be registered with the
endpoint. endpoint.
@ -1005,6 +1016,10 @@ class Flask(_PackageBoundObject):
endpoint endpoint
:param view_func: the function to call when serving a request to the :param view_func: the function to call when serving a request to the
provided endpoint provided endpoint
:param provide_automatic_options: controls whether the ``OPTIONS``
method should be added automatically. This can also be controlled
by setting the ``view_func.provide_automatic_options = False``
before adding the rule.
:param options: the options to be forwarded to the underlying :param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change :class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods to Werkzeug is handling of method options. methods
@ -1034,8 +1049,9 @@ class Flask(_PackageBoundObject):
# starting with Flask 0.8 the view_func object can disable and # starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling. # force-enable the automatic options handling.
provide_automatic_options = getattr(view_func, if provide_automatic_options is None:
'provide_automatic_options', None) provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
if provide_automatic_options is None: if provide_automatic_options is None:
if 'OPTIONS' not in methods: if 'OPTIONS' not in methods:
@ -1295,10 +1311,12 @@ class Flask(_PackageBoundObject):
def before_request(self, f): def before_request(self, f):
"""Registers a function to run before each request. """Registers a function to run before each request.
The function will be called without any arguments. For example, this can be used to open a database connection, or to load
If the function returns a non-None value, it's handled as the logged in user from the session.
if it was the return value from the view and further
request handling is stopped. The function will be called without any arguments. If it returns a
non-None value, the value is handled as if it was the return value from
the view, and further request handling is stopped.
""" """
self.before_request_funcs.setdefault(None, []).append(f) self.before_request_funcs.setdefault(None, []).append(f)
return f return f
@ -1354,7 +1372,7 @@ class Flask(_PackageBoundObject):
will have to surround the execution of these code by try/except will have to surround the execution of these code by try/except
statements and log occurring errors. statements and log occurring errors.
When a teardown function was called because of a exception it will When a teardown function was called because of an exception it will
be passed an error object. be passed an error object.
The return values of teardown functions are ignored. The return values of teardown functions are ignored.
@ -1417,9 +1435,17 @@ class Flask(_PackageBoundObject):
@setupmethod @setupmethod
def url_value_preprocessor(self, f): def url_value_preprocessor(self, f):
"""Registers a function as URL value preprocessor for all view """Register a URL value preprocessor function for all view
functions of the application. It's called before the view functions functions in the application. These functions will be called before the
are called and can modify the url values provided. :meth:`before_request` functions.
The function can modify the values captured from the matched url before
they are passed to the view. For example, this can be used to pop a
common language code value and place it in ``g`` rather than pass it to
every view.
The function is passed the endpoint name and values dict. The return
value is ignored.
""" """
self.url_value_preprocessors.setdefault(None, []).append(f) self.url_value_preprocessors.setdefault(None, []).append(f)
return f return f
@ -1434,15 +1460,17 @@ class Flask(_PackageBoundObject):
return f return f
def _find_error_handler(self, e): def _find_error_handler(self, e):
"""Finds a registered error handler for the request’s blueprint. """Find a registered error handler for a request in this order:
Otherwise falls back to the app, returns None if not a suitable blueprint handler for a specific code, app handler for a specific code,
handler is found. blueprint generic HTTPException handler, app generic HTTPException handler,
and returns None if a suitable handler is not found.
""" """
exc_class, code = self._get_exc_class_and_code(type(e)) exc_class, code = self._get_exc_class_and_code(type(e))
def find_handler(handler_map): def find_handler(handler_map):
if not handler_map: if not handler_map:
return return
for cls in exc_class.__mro__: for cls in exc_class.__mro__:
handler = handler_map.get(cls) handler = handler_map.get(cls)
if handler is not None: if handler is not None:
@ -1450,15 +1478,13 @@ class Flask(_PackageBoundObject):
handler_map[exc_class] = handler handler_map[exc_class] = handler
return handler return handler
# try blueprint handlers # check for any in blueprint or app
handler = find_handler(self.error_handler_spec for name, c in ((request.blueprint, code), (None, code),
.get(request.blueprint, {}) (request.blueprint, None), (None, None)):
.get(code)) handler = find_handler(self.error_handler_spec.get(name, {}).get(c))
if handler is not None:
return handler
# fall back to app handlers if handler:
return find_handler(self.error_handler_spec[None].get(code)) return handler
def handle_http_exception(self, e): def handle_http_exception(self, e):
"""Handles an HTTP exception. By default this will invoke the """Handles an HTTP exception. By default this will invoke the
@ -1695,62 +1721,106 @@ class Flask(_PackageBoundObject):
return False return False
def make_response(self, rv): def make_response(self, rv):
"""Converts the return value from a view function to a real """Convert the return value from a view function to an instance of
response object that is an instance of :attr:`response_class`. :attr:`response_class`.
The following types are allowed for `rv`: :param rv: the return value from the view function. The view function
must return a response. Returning ``None``, or the view ending
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}| without returning, is not allowed. The following types are allowed
for ``view_rv``:
======================= ===========================================
:attr:`response_class` the object is returned unchanged ``str`` (``unicode`` in Python 2)
:class:`str` a response object is created with the A response object is created with the string encoded to UTF-8
string as body as the body.
:class:`unicode` a response object is created with the
string encoded to utf-8 as body ``bytes`` (``str`` in Python 2)
a WSGI function the function is called as WSGI application A response object is created with the bytes as the body.
and buffered as response object
:class:`tuple` A tuple in the form ``(response, status, ``tuple``
headers)`` or ``(response, headers)`` Either ``(body, status, headers)``, ``(body, status)``, or
where `response` is any of the ``(body, headers)``, where ``body`` is any of the other types
types defined here, `status` is a string allowed here, ``status`` is a string or an integer, and
or an integer and `headers` is a list or ``headers`` is a dictionary or a list of ``(key, value)``
a dictionary with header values. tuples. If ``body`` is a :attr:`response_class` instance,
======================= =========================================== ``status`` overwrites the exiting value and ``headers`` are
extended.
:param rv: the return value from the view function
:attr:`response_class`
The object is returned unchanged.
other :class:`~werkzeug.wrappers.Response` class
The object is coerced to :attr:`response_class`.
:func:`callable`
The function is called as a WSGI application. The result is
used to create a response object.
.. versionchanged:: 0.9 .. versionchanged:: 0.9
Previously a tuple was interpreted as the arguments for the Previously a tuple was interpreted as the arguments for the
response object. response object.
""" """
status_or_headers = headers = None
if isinstance(rv, tuple):
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
if rv is None: status = headers = None
raise ValueError('View function did not return a response')
if isinstance(status_or_headers, (dict, list)): # unpack tuple returns
headers, status_or_headers = status_or_headers, None if isinstance(rv, (tuple, list)):
len_rv = len(rv)
# a 3-tuple is unpacked directly
if len_rv == 3:
rv, status, headers = rv
# decide if a 2-tuple has status or headers
elif len_rv == 2:
if isinstance(rv[1], (Headers, dict, tuple, list)):
rv, headers = rv
else:
rv, status = rv
# other sized tuples are not allowed
else:
raise TypeError(
'The view function did not return a valid response tuple.'
' The tuple must have the form (body, status, headers),'
' (body, status), or (body, headers).'
)
# the body must not be None
if rv is None:
raise TypeError(
'The view function did not return a valid response. The'
' function either returned None or ended without a return'
' statement.'
)
# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class): if not isinstance(rv, self.response_class):
# When we create a response object directly, we let the constructor
# set the headers and status. We do this because there can be
# some extra logic involved when creating these objects with
# specific values (like default content type selection).
if isinstance(rv, (text_type, bytes, bytearray)): if isinstance(rv, (text_type, bytes, bytearray)):
rv = self.response_class(rv, headers=headers, # let the response class set the status and headers instead of
status=status_or_headers) # waiting to do it manually, so that the class can handle any
headers = status_or_headers = None # special logic
rv = self.response_class(rv, status=status, headers=headers)
status = headers = None
else: else:
rv = self.response_class.force_type(rv, request.environ) # evaluate a WSGI callable, or coerce a different response
# class to the correct type
if status_or_headers is not None: try:
if isinstance(status_or_headers, string_types): rv = self.response_class.force_type(rv, request.environ)
rv.status = status_or_headers except TypeError as e:
new_error = TypeError(
'{e}\nThe view function did not return a valid'
' response. The return type must be a string, tuple,'
' Response instance, or WSGI callable, but it was a'
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
)
reraise(TypeError, new_error, sys.exc_info()[2])
# prefer the status if it was provided
if status is not None:
if isinstance(status, (text_type, bytes, bytearray)):
rv.status = status
else: else:
rv.status_code = status_or_headers rv.status_code = status
# extend existing headers with provided headers
if headers: if headers:
rv.headers.extend(headers) rv.headers.extend(headers)
@ -1813,16 +1883,16 @@ class Flask(_PackageBoundObject):
raise error raise error
def preprocess_request(self): def preprocess_request(self):
"""Called before the actual request dispatching and will """Called before the request is dispatched. Calls
call each :meth:`before_request` decorated function, passing no :attr:`url_value_preprocessors` registered with the app and the
arguments. current blueprint (if any). Then calls :attr:`before_request_funcs`
If any of these functions returns a value, it's handled as registered with the app and the blueprint.
if it was the return value from the view and further
request handling is stopped.
This also triggers the :meth:`url_value_preprocessor` functions before If any :meth:`before_request` handler returns a non-None value, the
the actual :meth:`before_request` functions are called. value is handled as if it was the return value from the view, and
further request handling is stopped.
""" """
bp = _request_ctx_stack.top.request.blueprint bp = _request_ctx_stack.top.request.blueprint
funcs = self.url_value_preprocessors.get(None, ()) funcs = self.url_value_preprocessors.get(None, ())
@ -1982,14 +2052,17 @@ class Flask(_PackageBoundObject):
exception context to start the response exception context to start the response
""" """
ctx = self.request_context(environ) ctx = self.request_context(environ)
ctx.push()
error = None error = None
try: try:
try: try:
ctx.push()
response = self.full_dispatch_request() response = self.full_dispatch_request()
except Exception as e: except Exception as e:
error = e error = e
response = self.handle_exception(e) response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response) return response(environ, start_response)
finally: finally:
if self.should_ignore_error(error): if self.should_ignore_error(error):

7
flask/blueprints.py

@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject):
warn_on_modifications = False warn_on_modifications = False
_got_registered_once = False _got_registered_once = False
#: Blueprint local JSON decoder class to use.
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
json_encoder = None
#: Blueprint local JSON decoder class to use.
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
json_decoder = None
def __init__(self, name, import_name, static_folder=None, def __init__(self, name, import_name, static_folder=None,
static_url_path=None, template_folder=None, static_url_path=None, template_folder=None,
url_prefix=None, subdomain=None, url_defaults=None, url_prefix=None, subdomain=None, url_defaults=None,

131
flask/cli.py

@ -11,41 +11,86 @@
import os import os
import sys import sys
from threading import Lock, Thread import traceback
from functools import update_wrapper from functools import update_wrapper
from operator import attrgetter
from threading import Lock, Thread
import click import click
from . import __version__
from ._compat import iteritems, reraise from ._compat import iteritems, reraise
from .globals import current_app
from .helpers import get_debug_flag from .helpers import get_debug_flag
from . import __version__ from ._compat import getargspec
class NoAppException(click.UsageError): class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded.""" """Raised if an application cannot be found or loaded."""
def find_best_app(module): def find_best_app(script_info, module):
"""Given a module instance this tries to find the best possible """Given a module instance this tries to find the best possible
application in the module or raises an exception. application in the module or raises an exception.
""" """
from . import Flask from . import Flask
# Search for the most common names first. # Search for the most common names first.
for attr_name in 'app', 'application': for attr_name in ('app', 'application'):
app = getattr(module, attr_name, None) app = getattr(module, attr_name, None)
if app is not None and isinstance(app, Flask): if isinstance(app, Flask):
return app return app
# Otherwise find the only object that is a Flask instance. # Otherwise find the only object that is a Flask instance.
matches = [v for k, v in iteritems(module.__dict__) matches = [
if isinstance(v, Flask)] v for k, v in iteritems(module.__dict__) if isinstance(v, Flask)
]
if len(matches) == 1: if len(matches) == 1:
return matches[0] return matches[0]
raise NoAppException('Failed to find application in module "%s". Are ' elif len(matches) > 1:
'you sure it contains a Flask application? Maybe ' raise NoAppException(
'you wrapped it in a WSGI middleware or you are ' 'Auto-detected multiple Flask applications in module "{module}".'
'using a factory function.' % module.__name__) ' Use "FLASK_APP={module}:name" to specify the correct'
' one.'.format(module=module.__name__)
)
# Search for app factory callables.
for attr_name in ('create_app', 'make_app'):
app_factory = getattr(module, attr_name, None)
if callable(app_factory):
try:
app = call_factory(app_factory, script_info)
if isinstance(app, Flask):
return app
except TypeError:
raise NoAppException(
'Auto-detected "{callable}()" in module "{module}", but '
'could not call it without specifying arguments.'.format(
callable=attr_name, module=module.__name__
)
)
raise NoAppException(
'Failed to find application in module "{module}". Are you sure '
'it contains a Flask application? Maybe you wrapped it in a WSGI '
'middleware.'.format(module=module.__name__)
)
def call_factory(func, script_info):
"""Checks if the given app factory function has an argument named
``script_info`` or just a single argument and calls the function passing
``script_info`` if so. Otherwise, calls the function without any arguments
and returns the result.
"""
arguments = getargspec(func).args
if 'script_info' in arguments:
return func(script_info=script_info)
elif len(arguments) == 1:
return func(script_info)
return func()
def prepare_exec_for_file(filename): def prepare_exec_for_file(filename):
@ -77,7 +122,7 @@ def prepare_exec_for_file(filename):
return '.'.join(module[::-1]) return '.'.join(module[::-1])
def locate_app(app_id): def locate_app(script_info, app_id):
"""Attempts to locate the application.""" """Attempts to locate the application."""
__traceback_hide__ = True __traceback_hide__ = True
if ':' in app_id: if ':' in app_id:
@ -92,7 +137,9 @@ def locate_app(app_id):
# Reraise the ImportError if it occurred within the imported module. # Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1. # Determine this by checking whether the trace has a depth > 1.
if sys.exc_info()[-1].tb_next: if sys.exc_info()[-1].tb_next:
raise stack_trace = traceback.format_exc()
raise NoAppException('There was an error trying to import'
' the app (%s):\n%s' % (module, stack_trace))
else: else:
raise NoAppException('The file/path provided (%s) does not appear' raise NoAppException('The file/path provided (%s) does not appear'
' to exist. Please verify the path is ' ' to exist. Please verify the path is '
@ -101,7 +148,7 @@ def locate_app(app_id):
mod = sys.modules[module] mod = sys.modules[module]
if app_obj is None: if app_obj is None:
app = find_best_app(mod) app = find_best_app(script_info, mod)
else: else:
app = getattr(mod, app_obj, None) app = getattr(mod, app_obj, None)
if app is None: if app is None:
@ -226,7 +273,7 @@ class ScriptInfo(object):
if self._loaded_app is not None: if self._loaded_app is not None:
return self._loaded_app return self._loaded_app
if self.create_app is not None: if self.create_app is not None:
rv = self.create_app(self) rv = call_factory(self.create_app, self)
else: else:
if not self.app_import_path: if not self.app_import_path:
raise NoAppException( raise NoAppException(
@ -234,7 +281,7 @@ class ScriptInfo(object):
'the FLASK_APP environment variable.\n\nFor more ' 'the FLASK_APP environment variable.\n\nFor more '
'information see ' 'information see '
'http://flask.pocoo.org/docs/latest/quickstart/') 'http://flask.pocoo.org/docs/latest/quickstart/')
rv = locate_app(self.app_import_path) rv = locate_app(self, self.app_import_path)
debug = get_debug_flag() debug = get_debug_flag()
if debug is not None: if debug is not None:
rv.debug = debug rv.debug = debug
@ -316,6 +363,7 @@ class FlaskGroup(AppGroup):
if add_default_commands: if add_default_commands:
self.add_command(run_command) self.add_command(run_command)
self.add_command(shell_command) self.add_command(shell_command)
self.add_command(routes_command)
self._loaded_plugin_commands = False self._loaded_plugin_commands = False
@ -368,7 +416,9 @@ class FlaskGroup(AppGroup):
# want the help page to break if the app does not exist. # want the help page to break if the app does not exist.
# If someone attempts to use the command we try to create # If someone attempts to use the command we try to create
# the app again and this will give us the error. # the app again and this will give us the error.
pass # However, we will not do so silently because that would confuse
# users.
traceback.print_exc()
return sorted(rv) return sorted(rv)
def main(self, *args, **kwargs): def main(self, *args, **kwargs):
@ -479,6 +529,53 @@ def shell_command():
code.interact(banner=banner, local=ctx) code.interact(banner=banner, local=ctx)
@click.command('routes', short_help='Show the routes for the app.')
@click.option(
'--sort', '-s',
type=click.Choice(('endpoint', 'methods', 'rule', 'match')),
default='endpoint',
help=(
'Method to sort routes by. "match" is the order that Flask will match '
'routes when dispatching a request.'
)
)
@click.option(
'--all-methods',
is_flag=True,
help="Show HEAD and OPTIONS methods."
)
@with_appcontext
def routes_command(sort, all_methods):
"""Show all registered routes with endpoints and methods."""
rules = list(current_app.url_map.iter_rules())
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
if sort in ('endpoint', 'rule'):
rules = sorted(rules, key=attrgetter(sort))
elif sort == 'methods':
rules = sorted(rules, key=lambda rule: sorted(rule.methods))
rule_methods = [
', '.join(sorted(rule.methods - ignored_methods)) for rule in rules
]
headers = ('Endpoint', 'Methods', 'Rule')
widths = (
max(len(rule.endpoint) for rule in rules),
max(len(methods) for methods in rule_methods),
max(len(rule.rule) for rule in rules),
)
widths = [max(len(h), w) for h, w in zip(headers, widths)]
row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths)
click.echo(row.format(*headers).strip())
click.echo(row.format(*('-' * width for width in widths)))
for rule, methods in zip(rules, rule_methods):
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
cli = FlaskGroup(help="""\ cli = FlaskGroup(help="""\
This shell command acts as general utility script for Flask applications. This shell command acts as general utility script for Flask applications.

82
flask/helpers.py

@ -10,6 +10,7 @@
""" """
import os import os
import socket
import sys import sys
import pkgutil import pkgutil
import posixpath import posixpath
@ -17,6 +18,7 @@ import mimetypes
from time import time from time import time
from zlib import adler32 from zlib import adler32
from threading import RLock from threading import RLock
import unicodedata
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from functools import update_wrapper from functools import update_wrapper
@ -330,6 +332,7 @@ def url_for(endpoint, **values):
values['_external'] = external values['_external'] = external
values['_anchor'] = anchor values['_anchor'] = anchor
values['_method'] = method values['_method'] = method
values['_scheme'] = scheme
return appctx.app.handle_url_build_error(error, endpoint, values) return appctx.app.handle_url_build_error(error, endpoint, values)
if anchor is not None: if anchor is not None:
@ -478,7 +481,12 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
The `attachment_filename` is preferred over `filename` for MIME-type The `attachment_filename` is preferred over `filename` for MIME-type
detection. detection.
:param filename_or_fp: the filename of the file to send in `latin-1`. .. versionchanged:: 0.13
UTF-8 filenames, as specified in `RFC 2231`_, are supported.
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
:param filename_or_fp: the filename of the file to send.
This is relative to the :attr:`~Flask.root_path` This is relative to the :attr:`~Flask.root_path`
if a relative path is specified. if a relative path is specified.
Alternatively a file object might be provided in Alternatively a file object might be provided in
@ -534,8 +542,19 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
if attachment_filename is None: if attachment_filename is None:
raise TypeError('filename unavailable, required for ' raise TypeError('filename unavailable, required for '
'sending as attachment') 'sending as attachment')
headers.add('Content-Disposition', 'attachment',
filename=attachment_filename) try:
attachment_filename = attachment_filename.encode('latin-1')
except UnicodeEncodeError:
filenames = {
'filename': unicodedata.normalize(
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
}
else:
filenames = {'filename': attachment_filename}
headers.add('Content-Disposition', 'attachment', **filenames)
if current_app.use_x_sendfile and filename: if current_app.use_x_sendfile and filename:
if file is not None: if file is not None:
@ -619,18 +638,24 @@ def safe_join(directory, *pathnames):
:raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed :raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
paths fall out of its boundaries. paths fall out of its boundaries.
""" """
parts = [directory]
for filename in pathnames: for filename in pathnames:
if filename != '': if filename != '':
filename = posixpath.normpath(filename) filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename: if (
raise NotFound() any(sep in filename for sep in _os_alt_seps)
if os.path.isabs(filename) or \ or os.path.isabs(filename)
filename == '..' or \ or filename == '..'
filename.startswith('../'): or filename.startswith('../')
):
raise NotFound() raise NotFound()
directory = os.path.join(directory, filename)
return directory parts.append(filename)
return posixpath.join(*parts)
def send_from_directory(directory, filename, **options): def send_from_directory(directory, filename, **options):
@ -958,3 +983,38 @@ def total_seconds(td):
:rtype: int :rtype: int
""" """
return td.days * 60 * 60 * 24 + td.seconds return td.days * 60 * 60 * 24 + td.seconds
def is_ip(value):
"""Determine if the given string is an IP address.
:param value: value to check
:type value: str
:return: True if string is an IP address
:rtype: bool
"""
for family in (socket.AF_INET, socket.AF_INET6):
try:
socket.inet_pton(family, value)
except socket.error:
pass
else:
return True
return False
def patch_vary_header(response, value):
"""Add a value to the ``Vary`` header if it is not already present."""
header = response.headers.get('Vary', '')
headers = [h for h in (h.strip() for h in header.split(',')) if h]
lower_value = value.lower()
if not any(h.lower() == lower_value for h in headers):
headers.append(value)
updated_header = ', '.join(headers)
response.headers['Vary'] = updated_header

27
flask/json.py

@ -91,9 +91,16 @@ class JSONDecoder(_json.JSONDecoder):
def _dump_arg_defaults(kwargs): def _dump_arg_defaults(kwargs):
"""Inject default arguments for dump functions.""" """Inject default arguments for dump functions."""
if current_app: if current_app:
kwargs.setdefault('cls', current_app.json_encoder) bp = current_app.blueprints.get(request.blueprint) if request else None
kwargs.setdefault(
'cls',
bp.json_encoder if bp and bp.json_encoder
else current_app.json_encoder
)
if not current_app.config['JSON_AS_ASCII']: if not current_app.config['JSON_AS_ASCII']:
kwargs.setdefault('ensure_ascii', False) kwargs.setdefault('ensure_ascii', False)
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS']) kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
else: else:
kwargs.setdefault('sort_keys', True) kwargs.setdefault('sort_keys', True)
@ -103,7 +110,12 @@ def _dump_arg_defaults(kwargs):
def _load_arg_defaults(kwargs): def _load_arg_defaults(kwargs):
"""Inject default arguments for load functions.""" """Inject default arguments for load functions."""
if current_app: if current_app:
kwargs.setdefault('cls', current_app.json_decoder) bp = current_app.blueprints.get(request.blueprint) if request else None
kwargs.setdefault(
'cls',
bp.json_decoder if bp and bp.json_decoder
else current_app.json_decoder
)
else: else:
kwargs.setdefault('cls', JSONDecoder) kwargs.setdefault('cls', JSONDecoder)
@ -236,11 +248,10 @@ def jsonify(*args, **kwargs):
Added support for serializing top-level arrays. This introduces a Added support for serializing top-level arrays. This introduces a
security risk in ancient browsers. See :ref:`json-security` for details. security risk in ancient browsers. See :ref:`json-security` for details.
This function's response will be pretty printed if it was not requested This function's response will be pretty printed if the
with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false. Flask app is running in debug mode. Compressed (not pretty) formatting
Compressed (not pretty) formatting currently means no indents and no currently means no indents and no spaces after separators.
spaces after separators.
.. versionadded:: 0.2 .. versionadded:: 0.2
""" """
@ -248,7 +259,7 @@ def jsonify(*args, **kwargs):
indent = None indent = None
separators = (',', ':') separators = (',', ':')
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_xhr: if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug:
indent = 2 indent = 2
separators = (', ', ': ') separators = (', ', ': ')

175
flask/sessions.py

@ -8,17 +8,20 @@
:copyright: (c) 2015 by Armin Ronacher. :copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import uuid
import hashlib import hashlib
from base64 import b64encode, b64decode import uuid
import warnings
from base64 import b64decode, b64encode
from datetime import datetime from datetime import datetime
from werkzeug.http import http_date, parse_date
from itsdangerous import BadSignature, URLSafeTimedSerializer
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
from werkzeug.http import http_date, parse_date
from flask.helpers import patch_vary_header
from . import Markup, json from . import Markup, json
from ._compat import iteritems, text_type from ._compat import iteritems, text_type
from .helpers import total_seconds from .helpers import is_ip, total_seconds
from itsdangerous import URLSafeTimedSerializer, BadSignature
class SessionMixin(object): class SessionMixin(object):
@ -47,6 +50,13 @@ class SessionMixin(object):
#: The default mixin implementation just hardcodes ``True`` in. #: The default mixin implementation just hardcodes ``True`` in.
modified = True modified = True
#: the accessed variable indicates whether or not the session object has
#: been accessed in that request. This allows flask to append a `Vary:
#: Cookie` header to the response if the session is being accessed. This
#: allows caching proxy servers, like Varnish, to use both the URL and the
#: session cookie as keys when caching pages, preventing multiple users
#: from being served the same cache.
accessed = True
class TaggedJSONSerializer(object): class TaggedJSONSerializer(object):
"""A customized JSON serializer that supports a few extra types that """A customized JSON serializer that supports a few extra types that
@ -175,8 +185,23 @@ class SecureCookieSession(CallbackDict, SessionMixin):
def __init__(self, initial=None): def __init__(self, initial=None):
def on_update(self): def on_update(self):
self.modified = True self.modified = True
CallbackDict.__init__(self, initial, on_update) self.accessed = True
super(SecureCookieSession, self).__init__(initial, on_update)
self.modified = False self.modified = False
self.accessed = False
def __getitem__(self, key):
self.accessed = True
return super(SecureCookieSession, self).__getitem__(key)
def get(self, key, default=None):
self.accessed = True
return super(SecureCookieSession, self).get(key, default)
def setdefault(self, key, default=None):
self.accessed = True
return super(SecureCookieSession, self).setdefault(key, default)
class NullSession(SecureCookieSession): class NullSession(SecureCookieSession):
@ -259,30 +284,62 @@ class SessionInterface(object):
return isinstance(obj, self.null_session_class) return isinstance(obj, self.null_session_class)
def get_cookie_domain(self, app): def get_cookie_domain(self, app):
"""Helpful helper method that returns the cookie domain that should """Returns the domain that should be set for the session cookie.
be used for the session cookie if session cookies are used.
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
falls back to detecting the domain based on ``SERVER_NAME``.
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
updated to avoid re-running the logic.
""" """
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
return app.config['SESSION_COOKIE_DOMAIN'] rv = app.config['SESSION_COOKIE_DOMAIN']
if app.config['SERVER_NAME'] is not None:
# chop off the port which is usually not supported by browsers # set explicitly, or cached from SERVER_NAME detection
rv = '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] # if False, return None
if rv is not None:
# Google chrome does not like cookies set to .localhost, so return rv if rv else None
# we just go with no domain then. Flask documents anyways that
# cross domain cookies need a fully qualified domain name rv = app.config['SERVER_NAME']
if rv == '.localhost':
rv = None # server name not set, cache False to return none next time
if not rv:
# If we infer the cookie domain from the server name we need app.config['SESSION_COOKIE_DOMAIN'] = False
# to check if we are in a subpath. In that case we can't return None
# set a cross domain cookie.
if rv is not None: # chop off the port which is usually not supported by browsers
path = self.get_cookie_path(app) # remove any leading '.' since we'll add that later
if path != '/': rv = rv.rsplit(':', 1)[0].lstrip('.')
rv = rv.lstrip('.')
if '.' not in rv:
return rv # Chrome doesn't allow names without a '.'
# this should only come up with localhost
# hack around this by not setting the name, and show a warning
warnings.warn(
'"{rv}" is not a valid cookie domain, it must contain a ".".'
' Add an entry to your hosts file, for example'
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
)
app.config['SESSION_COOKIE_DOMAIN'] = False
return None
ip = is_ip(rv)
if ip:
warnings.warn(
'The session cookie domain is an IP address. This may not work'
' as intended in some browsers. Add an entry to your hosts'
' file, for example "localhost.localdomain", and use that'
' instead.'
)
# if this is not an ip and app is mounted at the root, allow subdomain
# matching by adding a '.' prefix
if self.get_cookie_path(app) == '/' and not ip:
rv = '.' + rv
app.config['SESSION_COOKIE_DOMAIN'] = rv
return rv
def get_cookie_path(self, app): def get_cookie_path(self, app):
"""Returns the path for which the cookie should be valid. The """Returns the path for which the cookie should be valid. The
@ -316,22 +373,20 @@ class SessionInterface(object):
return datetime.utcnow() + app.permanent_session_lifetime return datetime.utcnow() + app.permanent_session_lifetime
def should_set_cookie(self, app, session): def should_set_cookie(self, app, session):
"""Indicates whether a cookie should be set now or not. This is """Used by session backends to determine if a ``Set-Cookie`` header
used by session backends to figure out if they should emit a should be set for this session cookie for this response. If the session
set-cookie header or not. The default behavior is controlled by has been modified, the cookie is set. If the session is permanent and
the ``SESSION_REFRESH_EACH_REQUEST`` config variable. If the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
it's set to ``False`` then a cookie is only set if the session is always set.
modified, if set to ``True`` it's always set if the session is
permanent.
This check is usually skipped if sessions get deleted. This check is usually skipped if the session was deleted.
.. versionadded:: 0.11 .. versionadded:: 0.11
""" """
if session.modified:
return True return session.modified or (
save_each = app.config['SESSION_REFRESH_EACH_REQUEST'] session.permanent and app.config['SESSION_REFRESH_EACH_REQUEST']
return save_each and session.permanent )
def open_session(self, app, request): def open_session(self, app, request):
"""This method has to be implemented and must either return ``None`` """This method has to be implemented and must either return ``None``
@ -397,22 +452,22 @@ class SecureCookieSessionInterface(SessionInterface):
domain = self.get_cookie_domain(app) domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app) path = self.get_cookie_path(app)
# Delete case. If there is no session we bail early. # If the session is modified to be empty, remove the cookie.
# If the session was modified to be empty we remove the # If the session is empty, return without setting the cookie.
# whole cookie.
if not session: if not session:
if session.modified: if session.modified:
response.delete_cookie(app.session_cookie_name, response.delete_cookie(
domain=domain, path=path) app.session_cookie_name,
domain=domain,
path=path
)
return return
# Modification case. There are upsides and downsides to # Add a "Vary: Cookie" header if the session was accessed at all.
# emitting a set-cookie header each request. The behavior if session.accessed:
# is controlled by the :meth:`should_set_cookie` method patch_vary_header(response, 'Cookie')
# which performs a quick check to figure out if the cookie
# should be set or not. This is controlled by the
# SESSION_REFRESH_EACH_REQUEST config flag as well as
# the permanent flag on the session itself.
if not self.should_set_cookie(app, session): if not self.should_set_cookie(app, session):
return return
@ -420,6 +475,12 @@ class SecureCookieSessionInterface(SessionInterface):
secure = self.get_cookie_secure(app) secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session) expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session)) val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie(app.session_cookie_name, val, response.set_cookie(
expires=expires, httponly=httponly, app.session_cookie_name,
domain=domain, path=path, secure=secure) val,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure
)

44
flask/views.py

@ -51,6 +51,9 @@ class View(object):
#: A list of methods this view can handle. #: A list of methods this view can handle.
methods = None methods = None
#: Setting this disables or force-enables the automatic options handling.
provide_automatic_options = None
#: The canonical way to decorate class-based views is to decorate the #: The canonical way to decorate class-based views is to decorate the
#: return value of as_view(). However since this moves parts of the #: return value of as_view(). However since this moves parts of the
#: logic from the class declaration to the place where it's hooked #: logic from the class declaration to the place where it's hooked
@ -99,37 +102,39 @@ class View(object):
view.__doc__ = cls.__doc__ view.__doc__ = cls.__doc__
view.__module__ = cls.__module__ view.__module__ = cls.__module__
view.methods = cls.methods view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options
return view return view
class MethodViewType(type): class MethodViewType(type):
"""Metaclass for :class:`MethodView` that determines what methods the view
defines.
"""
def __init__(cls, name, bases, d):
super(MethodViewType, cls).__init__(name, bases, d)
def __new__(cls, name, bases, d):
rv = type.__new__(cls, name, bases, d)
if 'methods' not in d: if 'methods' not in d:
methods = set(rv.methods or []) methods = set()
for key in d:
if key in http_method_funcs: for key in http_method_funcs:
if hasattr(cls, key):
methods.add(key.upper()) methods.add(key.upper())
# If we have no method at all in there we don't want to
# add a method list. (This is for instance the case for # If we have no method at all in there we don't want to add a
# the base class or another subclass of a base method view # method list. This is for instance the case for the base class
# that does not introduce new methods). # or another subclass of a base method view that does not introduce
# new methods.
if methods: if methods:
rv.methods = sorted(methods) cls.methods = methods
return rv
class MethodView(with_metaclass(MethodViewType, View)): class MethodView(with_metaclass(MethodViewType, View)):
"""Like a regular class-based view but that dispatches requests to """A class-based view that dispatches request methods to the corresponding
particular methods. For instance if you implement a method called class methods. For example, if you implement a ``get`` method, it will be
:meth:`get` it means it will respond to ``'GET'`` requests and used to handle ``GET`` requests. ::
the :meth:`dispatch_request` implementation will automatically
forward your request to that. Also :attr:`options` is set for you
automatically::
class CounterAPI(MethodView): class CounterAPI(MethodView):
def get(self): def get(self):
return session.get('counter', 0) return session.get('counter', 0)
@ -139,11 +144,14 @@ class MethodView(with_metaclass(MethodViewType, View)):
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter')) app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
""" """
def dispatch_request(self, *args, **kwargs): def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None) meth = getattr(self, request.method.lower(), None)
# If the request method is HEAD and we don't have a handler for it # If the request method is HEAD and we don't have a handler for it
# retry with GET. # retry with GET.
if meth is None and request.method == 'HEAD': if meth is None and request.method == 'HEAD':
meth = getattr(self, 'get', None) meth = getattr(self, 'get', None)
assert meth is not None, 'Unimplemented method %r' % request.method assert meth is not None, 'Unimplemented method %r' % request.method
return meth(*args, **kwargs) return meth(*args, **kwargs)

2
scripts/flask-07-upgrade.py

@ -5,7 +5,7 @@
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
This command line script scans a whole application tree and attempts to This command line script scans a whole application tree and attempts to
output an unified diff with all the changes that are necessary to easily output a unified diff with all the changes that are necessary to easily
upgrade the application to 0.7 and to not yield deprecation warnings. upgrade the application to 0.7 and to not yield deprecation warnings.
This will also attempt to find `after_request` functions that don't modify This will also attempt to find `after_request` functions that don't modify

3
setup.cfg

@ -8,4 +8,5 @@ universal = 1
license_file = LICENSE license_file = LICENSE
[tool:pytest] [tool:pytest]
norecursedirs = .* *.egg *.egg-info env* artwork docs minversion = 3.0
testpaths = tests

4
setup.py

@ -71,10 +71,10 @@ setup(
zip_safe=False, zip_safe=False,
platforms='any', platforms='any',
install_requires=[ install_requires=[
'Werkzeug>=0.7', 'Werkzeug>=0.9',
'Jinja2>=2.4', 'Jinja2>=2.4',
'itsdangerous>=0.21', 'itsdangerous>=0.21',
'click>=2.0', 'click>=4.0',
], ],
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',

2
test-requirements.txt

@ -1 +1,3 @@
tox tox
pytest
pytest-cov

71
tests/conftest.py

@ -13,6 +13,40 @@ import sys
import pkgutil import pkgutil
import pytest import pytest
import textwrap import textwrap
from flask import Flask as _Flask
class Flask(_Flask):
testing = True
secret_key = __name__
def make_response(self, rv):
if rv is None:
rv = ''
return super(Flask, self).make_response(rv)
@pytest.fixture
def app():
app = Flask(__name__)
return app
@pytest.fixture
def app_ctx(app):
with app.app_context() as ctx:
yield ctx
@pytest.fixture
def req_ctx(app):
with app.test_request_context() as ctx:
yield ctx
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture @pytest.fixture
@ -22,16 +56,17 @@ def test_apps(monkeypatch):
os.path.dirname(__file__), 'test_apps')) os.path.dirname(__file__), 'test_apps'))
) )
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def leak_detector(request): def leak_detector():
def ensure_clean_request_context(): yield
# make sure we're not leaking a request context since we are
# testing flask internally in debug mode in a few cases # make sure we're not leaking a request context since we are
leaks = [] # testing flask internally in debug mode in a few cases
while flask._request_ctx_stack.top is not None: leaks = []
leaks.append(flask._request_ctx_stack.pop()) while flask._request_ctx_stack.top is not None:
assert leaks == [] leaks.append(flask._request_ctx_stack.pop())
request.addfinalizer(ensure_clean_request_context) assert leaks == []
@pytest.fixture(params=(True, False)) @pytest.fixture(params=(True, False))
@ -62,12 +97,13 @@ def limit_loader(request, monkeypatch):
def get_loader(*args, **kwargs): def get_loader(*args, **kwargs):
return LimitedLoader(old_get_loader(*args, **kwargs)) return LimitedLoader(old_get_loader(*args, **kwargs))
monkeypatch.setattr(pkgutil, 'get_loader', get_loader) monkeypatch.setattr(pkgutil, 'get_loader', get_loader)
@pytest.fixture @pytest.fixture
def modules_tmpdir(tmpdir, monkeypatch): def modules_tmpdir(tmpdir, monkeypatch):
'''A tmpdir added to sys.path''' """A tmpdir added to sys.path."""
rv = tmpdir.mkdir('modules_tmpdir') rv = tmpdir.mkdir('modules_tmpdir')
monkeypatch.syspath_prepend(str(rv)) monkeypatch.syspath_prepend(str(rv))
return rv return rv
@ -81,10 +117,10 @@ def modules_tmpdir_prefix(modules_tmpdir, monkeypatch):
@pytest.fixture @pytest.fixture
def site_packages(modules_tmpdir, monkeypatch): def site_packages(modules_tmpdir, monkeypatch):
'''Create a fake site-packages''' """Create a fake site-packages."""
rv = modules_tmpdir \ rv = modules_tmpdir \
.mkdir('lib')\ .mkdir('lib') \
.mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info))\ .mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info)) \
.mkdir('site-packages') .mkdir('site-packages')
monkeypatch.syspath_prepend(str(rv)) monkeypatch.syspath_prepend(str(rv))
return rv return rv
@ -92,8 +128,9 @@ def site_packages(modules_tmpdir, monkeypatch):
@pytest.fixture @pytest.fixture
def install_egg(modules_tmpdir, monkeypatch): def install_egg(modules_tmpdir, monkeypatch):
'''Generate egg from package name inside base and put the egg into """Generate egg from package name inside base and put the egg into
sys.path''' sys.path."""
def inner(name, base=modules_tmpdir): def inner(name, base=modules_tmpdir):
if not isinstance(name, str): if not isinstance(name, str):
raise ValueError(name) raise ValueError(name)
@ -117,6 +154,7 @@ def install_egg(modules_tmpdir, monkeypatch):
egg_path, = modules_tmpdir.join('dist/').listdir() egg_path, = modules_tmpdir.join('dist/').listdir()
monkeypatch.syspath_prepend(str(egg_path)) monkeypatch.syspath_prepend(str(egg_path))
return egg_path return egg_path
return inner return inner
@ -124,6 +162,7 @@ def install_egg(modules_tmpdir, monkeypatch):
def purge_module(request): def purge_module(request):
def inner(name): def inner(name):
request.addfinalizer(lambda: sys.modules.pop(name, None)) request.addfinalizer(lambda: sys.modules.pop(name, None))
return inner return inner
@ -131,4 +170,4 @@ def purge_module(request):
def catch_deprecation_warnings(recwarn): def catch_deprecation_warnings(recwarn):
yield yield
gc.collect() gc.collect()
assert not recwarn.list assert not recwarn.list, '\n'.join(str(w.message) for w in recwarn.list)

96
tests/test_appctx.py

@ -14,8 +14,7 @@ import pytest
import flask import flask
def test_basic_url_generation(): def test_basic_url_generation(app):
app = flask.Flask(__name__)
app.config['SERVER_NAME'] = 'localhost' app.config['SERVER_NAME'] = 'localhost'
app.config['PREFERRED_URL_SCHEME'] = 'https' app.config['PREFERRED_URL_SCHEME'] = 'https'
@ -27,31 +26,33 @@ def test_basic_url_generation():
rv = flask.url_for('index') rv = flask.url_for('index')
assert rv == 'https://localhost/' assert rv == 'https://localhost/'
def test_url_generation_requires_server_name():
app = flask.Flask(__name__) def test_url_generation_requires_server_name(app):
with app.app_context(): with app.app_context():
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
flask.url_for('index') flask.url_for('index')
def test_url_generation_without_context_fails(): def test_url_generation_without_context_fails():
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
flask.url_for('index') flask.url_for('index')
def test_request_context_means_app_context():
app = flask.Flask(__name__) def test_request_context_means_app_context(app):
with app.test_request_context(): with app.test_request_context():
assert flask.current_app._get_current_object() == app assert flask.current_app._get_current_object() == app
assert flask._app_ctx_stack.top is None assert flask._app_ctx_stack.top is None
def test_app_context_provides_current_app():
app = flask.Flask(__name__) def test_app_context_provides_current_app(app):
with app.app_context(): with app.app_context():
assert flask.current_app._get_current_object() == app assert flask.current_app._get_current_object() == app
assert flask._app_ctx_stack.top is None assert flask._app_ctx_stack.top is None
def test_app_tearing_down():
def test_app_tearing_down(app):
cleanup_stuff = [] cleanup_stuff = []
app = flask.Flask(__name__)
@app.teardown_appcontext @app.teardown_appcontext
def cleanup(exception): def cleanup(exception):
cleanup_stuff.append(exception) cleanup_stuff.append(exception)
@ -61,9 +62,10 @@ def test_app_tearing_down():
assert cleanup_stuff == [None] assert cleanup_stuff == [None]
def test_app_tearing_down_with_previous_exception():
def test_app_tearing_down_with_previous_exception(app):
cleanup_stuff = [] cleanup_stuff = []
app = flask.Flask(__name__)
@app.teardown_appcontext @app.teardown_appcontext
def cleanup(exception): def cleanup(exception):
cleanup_stuff.append(exception) cleanup_stuff.append(exception)
@ -78,9 +80,10 @@ def test_app_tearing_down_with_previous_exception():
assert cleanup_stuff == [None] assert cleanup_stuff == [None]
def test_app_tearing_down_with_handled_exception():
def test_app_tearing_down_with_handled_exception(app):
cleanup_stuff = [] cleanup_stuff = []
app = flask.Flask(__name__)
@app.teardown_appcontext @app.teardown_appcontext
def cleanup(exception): def cleanup(exception):
cleanup_stuff.append(exception) cleanup_stuff.append(exception)
@ -93,46 +96,49 @@ def test_app_tearing_down_with_handled_exception():
assert cleanup_stuff == [None] assert cleanup_stuff == [None]
def test_app_ctx_globals_methods():
app = flask.Flask(__name__) def test_app_ctx_globals_methods(app, app_ctx):
with app.app_context(): # get
# get assert flask.g.get('foo') is None
assert flask.g.get('foo') is None assert flask.g.get('foo', 'bar') == 'bar'
assert flask.g.get('foo', 'bar') == 'bar' # __contains__
# __contains__ assert 'foo' not in flask.g
assert 'foo' not in flask.g flask.g.foo = 'bar'
flask.g.foo = 'bar' assert 'foo' in flask.g
assert 'foo' in flask.g # setdefault
# setdefault flask.g.setdefault('bar', 'the cake is a lie')
flask.g.setdefault('bar', 'the cake is a lie') flask.g.setdefault('bar', 'hello world')
flask.g.setdefault('bar', 'hello world') assert flask.g.bar == 'the cake is a lie'
assert flask.g.bar == 'the cake is a lie' # pop
# pop assert flask.g.pop('bar') == 'the cake is a lie'
assert flask.g.pop('bar') == 'the cake is a lie' with pytest.raises(KeyError):
with pytest.raises(KeyError): flask.g.pop('bar')
flask.g.pop('bar') assert flask.g.pop('bar', 'more cake') == 'more cake'
assert flask.g.pop('bar', 'more cake') == 'more cake' # __iter__
# __iter__ assert list(flask.g) == ['foo']
assert list(flask.g) == ['foo']
def test_custom_app_ctx_globals_class(): def test_custom_app_ctx_globals_class(app):
class CustomRequestGlobals(object): class CustomRequestGlobals(object):
def __init__(self): def __init__(self):
self.spam = 'eggs' self.spam = 'eggs'
app = flask.Flask(__name__)
app.app_ctx_globals_class = CustomRequestGlobals app.app_ctx_globals_class = CustomRequestGlobals
with app.app_context(): with app.app_context():
assert flask.render_template_string('{{ g.spam }}') == 'eggs' assert flask.render_template_string('{{ g.spam }}') == 'eggs'
def test_context_refcounts():
def test_context_refcounts(app, client):
called = [] called = []
app = flask.Flask(__name__)
@app.teardown_request @app.teardown_request
def teardown_req(error=None): def teardown_req(error=None):
called.append('request') called.append('request')
@app.teardown_appcontext @app.teardown_appcontext
def teardown_app(error=None): def teardown_app(error=None):
called.append('app') called.append('app')
@app.route('/') @app.route('/')
def index(): def index():
with flask._app_ctx_stack.top: with flask._app_ctx_stack.top:
@ -141,16 +147,16 @@ def test_context_refcounts():
env = flask._request_ctx_stack.top.request.environ env = flask._request_ctx_stack.top.request.environ
assert env['werkzeug.request'] is not None assert env['werkzeug.request'] is not None
return u'' return u''
c = app.test_client()
res = c.get('/') res = client.get('/')
assert res.status_code == 200 assert res.status_code == 200
assert res.data == b'' assert res.data == b''
assert called == ['request', 'app'] assert called == ['request', 'app']
def test_clean_pop(): def test_clean_pop(app):
app.testing = False
called = [] called = []
app = flask.Flask(__name__)
@app.teardown_request @app.teardown_request
def teardown_req(error=None): def teardown_req(error=None):
@ -166,5 +172,5 @@ def test_clean_pop():
except ZeroDivisionError: except ZeroDivisionError:
pass pass
assert called == ['test_appctx', 'TEARDOWN'] assert called == ['conftest', 'TEARDOWN']
assert not flask.current_app assert not flask.current_app

1026
tests/test_basic.py

File diff suppressed because it is too large Load Diff

464
tests/test_blueprints.py

@ -18,7 +18,7 @@ from werkzeug.http import parse_cache_control_header
from jinja2 import TemplateNotFound from jinja2 import TemplateNotFound
def test_blueprint_specific_error_handling(): def test_blueprint_specific_error_handling(app, client):
frontend = flask.Blueprint('frontend', __name__) frontend = flask.Blueprint('frontend', __name__)
backend = flask.Blueprint('backend', __name__) backend = flask.Blueprint('backend', __name__)
sideend = flask.Blueprint('sideend', __name__) sideend = flask.Blueprint('sideend', __name__)
@ -43,7 +43,6 @@ def test_blueprint_specific_error_handling():
def sideend_no(): def sideend_no():
flask.abort(403) flask.abort(403)
app = flask.Flask(__name__)
app.register_blueprint(frontend) app.register_blueprint(frontend)
app.register_blueprint(backend) app.register_blueprint(backend)
app.register_blueprint(sideend) app.register_blueprint(sideend)
@ -52,15 +51,15 @@ def test_blueprint_specific_error_handling():
def app_forbidden(e): def app_forbidden(e):
return 'application itself says no', 403 return 'application itself says no', 403
c = app.test_client() assert client.get('/frontend-no').data == b'frontend says no'
assert client.get('/backend-no').data == b'backend says no'
assert client.get('/what-is-a-sideend').data == b'application itself says no'
assert c.get('/frontend-no').data == b'frontend says no'
assert c.get('/backend-no').data == b'backend says no'
assert c.get('/what-is-a-sideend').data == b'application itself says no'
def test_blueprint_specific_user_error_handling(): def test_blueprint_specific_user_error_handling(app, client):
class MyDecoratorException(Exception): class MyDecoratorException(Exception):
pass pass
class MyFunctionException(Exception): class MyFunctionException(Exception):
pass pass
@ -74,24 +73,48 @@ def test_blueprint_specific_user_error_handling():
def my_function_exception_handler(e): def my_function_exception_handler(e):
assert isinstance(e, MyFunctionException) assert isinstance(e, MyFunctionException)
return 'bam' return 'bam'
blue.register_error_handler(MyFunctionException, my_function_exception_handler) blue.register_error_handler(MyFunctionException, my_function_exception_handler)
@blue.route('/decorator') @blue.route('/decorator')
def blue_deco_test(): def blue_deco_test():
raise MyDecoratorException() raise MyDecoratorException()
@blue.route('/function') @blue.route('/function')
def blue_func_test(): def blue_func_test():
raise MyFunctionException() raise MyFunctionException()
app = flask.Flask(__name__)
app.register_blueprint(blue) app.register_blueprint(blue)
c = app.test_client() assert client.get('/decorator').data == b'boom'
assert client.get('/function').data == b'bam'
def test_blueprint_app_error_handling(app, client):
errors = flask.Blueprint('errors', __name__)
@errors.app_errorhandler(403)
def forbidden_handler(e):
return 'you shall not pass', 403
@app.route('/forbidden')
def app_forbidden():
flask.abort(403)
forbidden_bp = flask.Blueprint('forbidden_bp', __name__)
@forbidden_bp.route('/nope')
def bp_forbidden():
flask.abort(403)
assert c.get('/decorator').data == b'boom' app.register_blueprint(errors)
assert c.get('/function').data == b'bam' app.register_blueprint(forbidden_bp)
def test_blueprint_url_definitions(): assert client.get('/forbidden').data == b'you shall not pass'
assert client.get('/nope').data == b'you shall not pass'
def test_blueprint_url_definitions(app, client):
bp = flask.Blueprint('test', __name__) bp = flask.Blueprint('test', __name__)
@bp.route('/foo', defaults={'baz': 42}) @bp.route('/foo', defaults={'baz': 42})
@ -102,17 +125,16 @@ def test_blueprint_url_definitions():
def bar(bar): def bar(bar):
return text_type(bar) return text_type(bar)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23})
app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19})
c = app.test_client() assert client.get('/1/foo').data == b'23/42'
assert c.get('/1/foo').data == b'23/42' assert client.get('/2/foo').data == b'19/42'
assert c.get('/2/foo').data == b'19/42' assert client.get('/1/bar').data == b'23'
assert c.get('/1/bar').data == b'23' assert client.get('/2/bar').data == b'19'
assert c.get('/2/bar').data == b'19'
def test_blueprint_url_processors(): def test_blueprint_url_processors(app, client):
bp = flask.Blueprint('frontend', __name__, url_prefix='/<lang_code>') bp = flask.Blueprint('frontend', __name__, url_prefix='/<lang_code>')
@bp.url_defaults @bp.url_defaults
@ -131,28 +153,26 @@ def test_blueprint_url_processors():
def about(): def about():
return flask.url_for('.index') return flask.url_for('.index')
app = flask.Flask(__name__)
app.register_blueprint(bp) app.register_blueprint(bp)
c = app.test_client() assert client.get('/de/').data == b'/de/about'
assert client.get('/de/about').data == b'/de/'
assert c.get('/de/').data == b'/de/about'
assert c.get('/de/about').data == b'/de/'
def test_templates_and_static(test_apps): def test_templates_and_static(test_apps):
from blueprintapp import app from blueprintapp import app
c = app.test_client() client = app.test_client()
rv = c.get('/') rv = client.get('/')
assert rv.data == b'Hello from the Frontend' assert rv.data == b'Hello from the Frontend'
rv = c.get('/admin/') rv = client.get('/admin/')
assert rv.data == b'Hello from the Admin' assert rv.data == b'Hello from the Admin'
rv = c.get('/admin/index2') rv = client.get('/admin/index2')
assert rv.data == b'Hello from the Admin' assert rv.data == b'Hello from the Admin'
rv = c.get('/admin/static/test.txt') rv = client.get('/admin/static/test.txt')
assert rv.data.strip() == b'Admin File' assert rv.data.strip() == b'Admin File'
rv.close() rv.close()
rv = c.get('/admin/static/css/test.css') rv = client.get('/admin/static/css/test.css')
assert rv.data.strip() == b'/* nested file */' assert rv.data.strip() == b'/* nested file */'
rv.close() rv.close()
@ -163,7 +183,7 @@ def test_templates_and_static(test_apps):
if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age: if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age:
expected_max_age = 7200 expected_max_age = 7200
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age
rv = c.get('/admin/static/css/test.css') rv = client.get('/admin/static/css/test.css')
cc = parse_cache_control_header(rv.headers['Cache-Control']) cc = parse_cache_control_header(rv.headers['Cache-Control'])
assert cc.max_age == expected_max_age assert cc.max_age == expected_max_age
rv.close() rv.close()
@ -181,8 +201,8 @@ def test_templates_and_static(test_apps):
with flask.Flask(__name__).test_request_context(): with flask.Flask(__name__).test_request_context():
assert flask.render_template('nested/nested.txt') == 'I\'m nested' assert flask.render_template('nested/nested.txt') == 'I\'m nested'
def test_default_static_cache_timeout():
app = flask.Flask(__name__) def test_default_static_cache_timeout(app):
class MyBlueprint(flask.Blueprint): class MyBlueprint(flask.Blueprint):
def get_send_file_max_age(self, filename): def get_send_file_max_age(self, filename):
return 100 return 100
@ -205,12 +225,14 @@ def test_default_static_cache_timeout():
finally: finally:
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
def test_templates_list(test_apps): def test_templates_list(test_apps):
from blueprintapp import app from blueprintapp import app
templates = sorted(app.jinja_env.list_templates()) templates = sorted(app.jinja_env.list_templates())
assert templates == ['admin/index.html', 'frontend/index.html'] assert templates == ['admin/index.html', 'frontend/index.html']
def test_dotted_names():
def test_dotted_names(app, client):
frontend = flask.Blueprint('myapp.frontend', __name__) frontend = flask.Blueprint('myapp.frontend', __name__)
backend = flask.Blueprint('myapp.backend', __name__) backend = flask.Blueprint('myapp.backend', __name__)
@ -226,18 +248,15 @@ def test_dotted_names():
def backend_index(): def backend_index():
return flask.url_for('myapp.frontend.frontend_index') return flask.url_for('myapp.frontend.frontend_index')
app = flask.Flask(__name__)
app.register_blueprint(frontend) app.register_blueprint(frontend)
app.register_blueprint(backend) app.register_blueprint(backend)
c = app.test_client() assert client.get('/fe').data.strip() == b'/be'
assert c.get('/fe').data.strip() == b'/be' assert client.get('/fe2').data.strip() == b'/fe'
assert c.get('/fe2').data.strip() == b'/fe' assert client.get('/be').data.strip() == b'/fe'
assert c.get('/be').data.strip() == b'/fe'
def test_dotted_names_from_app(): def test_dotted_names_from_app(app, client):
app = flask.Flask(__name__)
app.testing = True
test = flask.Blueprint('test', __name__) test = flask.Blueprint('test', __name__)
@app.route('/') @app.route('/')
@ -250,11 +269,11 @@ def test_dotted_names_from_app():
app.register_blueprint(test) app.register_blueprint(test)
with app.test_client() as c: rv = client.get('/')
rv = c.get('/') assert rv.data == b'/test/'
assert rv.data == b'/test/'
def test_empty_url_defaults(): def test_empty_url_defaults(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.route('/', defaults={'page': 1}) @bp.route('/', defaults={'page': 1})
@ -262,15 +281,13 @@ def test_empty_url_defaults():
def something(page): def something(page):
return str(page) return str(page)
app = flask.Flask(__name__)
app.register_blueprint(bp) app.register_blueprint(bp)
c = app.test_client() assert client.get('/').data == b'1'
assert c.get('/').data == b'1' assert client.get('/page/2').data == b'2'
assert c.get('/page/2').data == b'2'
def test_route_decorator_custom_endpoint():
def test_route_decorator_custom_endpoint(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.route('/foo') @bp.route('/foo')
@ -289,21 +306,20 @@ def test_route_decorator_custom_endpoint():
def bar_foo(): def bar_foo():
return flask.request.endpoint return flask.request.endpoint
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.request.endpoint return flask.request.endpoint
c = app.test_client() assert client.get('/').data == b'index'
assert c.get('/').data == b'index' assert client.get('/py/foo').data == b'bp.foo'
assert c.get('/py/foo').data == b'bp.foo' assert client.get('/py/bar').data == b'bp.bar'
assert c.get('/py/bar').data == b'bp.bar' assert client.get('/py/bar/123').data == b'bp.123'
assert c.get('/py/bar/123').data == b'bp.123' assert client.get('/py/bar/foo').data == b'bp.bar_foo'
assert c.get('/py/bar/foo').data == b'bp.bar_foo'
def test_route_decorator_custom_endpoint_with_dots():
def test_route_decorator_custom_endpoint_with_dots(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.route('/foo') @bp.route('/foo')
@ -344,231 +360,461 @@ def test_route_decorator_custom_endpoint_with_dots():
lambda: None lambda: None
) )
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
c = app.test_client() assert client.get('/py/foo').data == b'bp.foo'
assert c.get('/py/foo').data == b'bp.foo'
# The rule's didn't actually made it through # The rule's didn't actually made it through
rv = c.get('/py/bar') rv = client.get('/py/bar')
assert rv.status_code == 404 assert rv.status_code == 404
rv = c.get('/py/bar/123') rv = client.get('/py/bar/123')
assert rv.status_code == 404 assert rv.status_code == 404
def test_template_filter():
def test_endpoint_decorator(app, client):
from werkzeug.routing import Rule
app.url_map.add(Rule('/foo', endpoint='bar'))
bp = flask.Blueprint('bp', __name__)
@bp.endpoint('bar')
def foobar():
return flask.request.endpoint
app.register_blueprint(bp, url_prefix='/bp_prefix')
assert client.get('/foo').data == b'bar'
assert client.get('/bp_prefix/bar').status_code == 404
def test_template_filter(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter() @bp.app_template_filter()
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'my_reverse' in app.jinja_env.filters.keys() assert 'my_reverse' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['my_reverse'] == my_reverse assert app.jinja_env.filters['my_reverse'] == my_reverse
assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba'
def test_add_template_filter():
def test_add_template_filter(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
bp.add_app_template_filter(my_reverse) bp.add_app_template_filter(my_reverse)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'my_reverse' in app.jinja_env.filters.keys() assert 'my_reverse' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['my_reverse'] == my_reverse assert app.jinja_env.filters['my_reverse'] == my_reverse
assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba'
def test_template_filter_with_name():
def test_template_filter_with_name(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter('strrev') @bp.app_template_filter('strrev')
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'strrev' in app.jinja_env.filters.keys() assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse assert app.jinja_env.filters['strrev'] == my_reverse
assert app.jinja_env.filters['strrev']('abcd') == 'dcba' assert app.jinja_env.filters['strrev']('abcd') == 'dcba'
def test_add_template_filter_with_name():
def test_add_template_filter_with_name(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
bp.add_app_template_filter(my_reverse, 'strrev') bp.add_app_template_filter(my_reverse, 'strrev')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'strrev' in app.jinja_env.filters.keys() assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse assert app.jinja_env.filters['strrev'] == my_reverse
assert app.jinja_env.filters['strrev']('abcd') == 'dcba' assert app.jinja_env.filters['strrev']('abcd') == 'dcba'
def test_template_filter_with_template():
def test_template_filter_with_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter() @bp.app_template_filter()
def super_reverse(s): def super_reverse(s):
return s[::-1] return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_template_filter_after_route_with_template():
app = flask.Flask(__name__) def test_template_filter_after_route_with_template(app, client):
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter() @bp.app_template_filter()
def super_reverse(s): def super_reverse(s):
return s[::-1] return s[::-1]
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
rv = app.test_client().get('/') rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_add_template_filter_with_template():
def test_add_template_filter_with_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def super_reverse(s): def super_reverse(s):
return s[::-1] return s[::-1]
bp.add_app_template_filter(super_reverse) bp.add_app_template_filter(super_reverse)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_template_filter_with_name_and_template():
def test_template_filter_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter('super_reverse') @bp.app_template_filter('super_reverse')
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_add_template_filter_with_name_and_template():
def test_add_template_filter_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
bp.add_app_template_filter(my_reverse, 'super_reverse') bp.add_app_template_filter(my_reverse, 'super_reverse')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_template_test():
def test_template_test(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_test() @bp.app_template_test()
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'is_boolean' in app.jinja_env.tests.keys() assert 'is_boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['is_boolean'] == is_boolean assert app.jinja_env.tests['is_boolean'] == is_boolean
assert app.jinja_env.tests['is_boolean'](False) assert app.jinja_env.tests['is_boolean'](False)
def test_add_template_test():
def test_add_template_test(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
bp.add_app_template_test(is_boolean) bp.add_app_template_test(is_boolean)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'is_boolean' in app.jinja_env.tests.keys() assert 'is_boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['is_boolean'] == is_boolean assert app.jinja_env.tests['is_boolean'] == is_boolean
assert app.jinja_env.tests['is_boolean'](False) assert app.jinja_env.tests['is_boolean'](False)
def test_template_test_with_name():
def test_template_test_with_name(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_test('boolean') @bp.app_template_test('boolean')
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'boolean' in app.jinja_env.tests.keys() assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean assert app.jinja_env.tests['boolean'] == is_boolean
assert app.jinja_env.tests['boolean'](False) assert app.jinja_env.tests['boolean'](False)
def test_add_template_test_with_name():
def test_add_template_test_with_name(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
bp.add_app_template_test(is_boolean, 'boolean') bp.add_app_template_test(is_boolean, 'boolean')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
assert 'boolean' in app.jinja_env.tests.keys() assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean assert app.jinja_env.tests['boolean'] == is_boolean
assert app.jinja_env.tests['boolean'](False) assert app.jinja_env.tests['boolean'](False)
def test_template_test_with_template():
def test_template_test_with_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_test() @bp.app_template_test()
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_template_test_after_route_with_template():
app = flask.Flask(__name__) def test_template_test_after_route_with_template(app, client):
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_test() @bp.app_template_test()
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
rv = app.test_client().get('/') rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_add_template_test_with_template():
def test_add_template_test_with_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
bp.add_app_template_test(boolean) bp.add_app_template_test(boolean)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_template_test_with_name_and_template():
def test_template_test_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.app_template_test('boolean') @bp.app_template_test('boolean')
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_add_template_test_with_name_and_template():
def test_add_template_test_with_name_and_template(app, client):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
bp.add_app_template_test(is_boolean, 'boolean') bp.add_app_template_test(is_boolean, 'boolean')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py') app.register_blueprint(bp, url_prefix='/py')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_context_processing(app, client):
answer_bp = flask.Blueprint('answer_bp', __name__)
template_string = lambda: flask.render_template_string(
'{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}'
'{% if answer %}{{ answer }} is the answer.{% endif %}'
)
# App global context processor
@answer_bp.app_context_processor
def not_answer_context_processor():
return {'notanswer': 43}
# Blueprint local context processor
@answer_bp.context_processor
def answer_context_processor():
return {'answer': 42}
# Setup endpoints for testing
@answer_bp.route('/bp')
def bp_page():
return template_string()
@app.route('/')
def app_page():
return template_string()
# Register the blueprint
app.register_blueprint(answer_bp)
app_page_bytes = client.get('/').data
answer_page_bytes = client.get('/bp').data
assert b'43' in app_page_bytes
assert b'42' not in app_page_bytes
assert b'42' in answer_page_bytes
assert b'43' in answer_page_bytes
def test_template_global(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_global()
def get_answer():
return 42
# Make sure the function is not in the jinja_env already
assert 'get_answer' not in app.jinja_env.globals.keys()
app.register_blueprint(bp)
# Tests
assert 'get_answer' in app.jinja_env.globals.keys()
assert app.jinja_env.globals['get_answer'] is get_answer
assert app.jinja_env.globals['get_answer']() == 42
with app.app_context():
rv = flask.render_template_string('{{ get_answer() }}')
assert rv == '42'
def test_request_processing(app, client):
bp = flask.Blueprint('bp', __name__)
evts = []
@bp.before_request
def before_bp():
evts.append('before')
@bp.after_request
def after_bp(response):
response.data += b'|after'
evts.append('after')
return response
@bp.teardown_request
def teardown_bp(exc):
evts.append('teardown')
# Setup routes for testing
@bp.route('/bp')
def bp_endpoint():
return 'request'
app.register_blueprint(bp)
assert evts == []
rv = client.get('/bp')
assert rv.data == b'request|after'
assert evts == ['before', 'after', 'teardown']
def test_app_request_processing(app, client):
bp = flask.Blueprint('bp', __name__)
evts = []
@bp.before_app_first_request
def before_first_request():
evts.append('first')
@bp.before_app_request
def before_app():
evts.append('before')
@bp.after_app_request
def after_app(response):
response.data += b'|after'
evts.append('after')
return response
@bp.teardown_app_request
def teardown_app(exc):
evts.append('teardown')
app.register_blueprint(bp)
# Setup routes for testing
@app.route('/')
def bp_endpoint():
return 'request'
# before first request
assert evts == []
# first request
resp = client.get('/').data
assert resp == b'request|after'
assert evts == ['first', 'before', 'after', 'teardown']
# second request
resp = client.get('/').data
assert resp == b'request|after'
assert evts == ['first'] + ['before', 'after', 'teardown'] * 2
def test_app_url_processors(app, client):
bp = flask.Blueprint('bp', __name__)
# Register app-wide url defaults and preprocessor on blueprint
@bp.app_url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', flask.g.lang_code)
@bp.app_url_value_preprocessor
def pull_lang_code(endpoint, values):
flask.g.lang_code = values.pop('lang_code')
# Register route rules at the app level
@app.route('/<lang_code>/')
def index():
return flask.url_for('about')
@app.route('/<lang_code>/about')
def about():
return flask.url_for('index')
app.register_blueprint(bp)
assert client.get('/de/').data == b'/de/about'
assert client.get('/de/about').data == b'/de/'

191
tests/test_cli.py

@ -14,17 +14,23 @@
from __future__ import absolute_import, print_function from __future__ import absolute_import, print_function
import os import os
import sys import sys
from functools import partial
import click import click
import pytest import pytest
from click.testing import CliRunner from click.testing import CliRunner
from flask import Flask, current_app from flask import Flask, current_app
from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ from flask.cli import cli, AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \ find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \
find_default_import_path, get_version find_default_import_path, get_version
@pytest.fixture
def runner():
return CliRunner()
def test_cli_name(test_apps): def test_cli_name(test_apps):
"""Make sure the CLI object's name is the app's name and not the app itself""" """Make sure the CLI object's name is the app's name and not the app itself"""
from cliapp.app import testapp from cliapp.app import testapp
@ -33,26 +39,90 @@ def test_cli_name(test_apps):
def test_find_best_app(test_apps): def test_find_best_app(test_apps):
"""Test if `find_best_app` behaves as expected with different combinations of input.""" """Test if `find_best_app` behaves as expected with different combinations of input."""
script_info = ScriptInfo()
class Module: class Module:
app = Flask('appname') app = Flask('appname')
assert find_best_app(Module) == Module.app
assert find_best_app(script_info, Module) == Module.app
class Module: class Module:
application = Flask('appname') application = Flask('appname')
assert find_best_app(Module) == Module.application
assert find_best_app(script_info, Module) == Module.application
class Module: class Module:
myapp = Flask('appname') myapp = Flask('appname')
assert find_best_app(Module) == Module.myapp
assert find_best_app(script_info, Module) == Module.myapp
class Module:
@staticmethod
def create_app():
return Flask('appname')
assert isinstance(find_best_app(script_info, Module), Flask)
assert find_best_app(script_info, Module).name == 'appname'
class Module:
@staticmethod
def create_app(foo):
return Flask('appname')
assert isinstance(find_best_app(script_info, Module), Flask)
assert find_best_app(script_info, Module).name == 'appname'
class Module:
@staticmethod
def create_app(foo=None, script_info=None):
return Flask('appname')
assert isinstance(find_best_app(script_info, Module), Flask)
assert find_best_app(script_info, Module).name == 'appname'
class Module:
@staticmethod
def make_app():
return Flask('appname')
assert isinstance(find_best_app(script_info, Module), Flask)
assert find_best_app(script_info, Module).name == 'appname'
class Module:
myapp = Flask('appname1')
@staticmethod
def create_app():
return Flask('appname2')
assert find_best_app(script_info, Module) == Module.myapp
class Module:
myapp = Flask('appname1')
@staticmethod
def create_app():
return Flask('appname2')
assert find_best_app(script_info, Module) == Module.myapp
class Module: class Module:
pass pass
pytest.raises(NoAppException, find_best_app, Module)
pytest.raises(NoAppException, find_best_app, script_info, Module)
class Module: class Module:
myapp1 = Flask('appname1') myapp1 = Flask('appname1')
myapp2 = Flask('appname2') myapp2 = Flask('appname2')
pytest.raises(NoAppException, find_best_app, Module)
pytest.raises(NoAppException, find_best_app, script_info, Module)
class Module:
@staticmethod
def create_app(foo, bar):
return Flask('appname2')
pytest.raises(NoAppException, find_best_app, script_info, Module)
def test_prepare_exec_for_file(test_apps): def test_prepare_exec_for_file(test_apps):
@ -77,13 +147,18 @@ def test_prepare_exec_for_file(test_apps):
def test_locate_app(test_apps): def test_locate_app(test_apps):
"""Test of locate_app.""" """Test of locate_app."""
assert locate_app("cliapp.app").name == "testapp" script_info = ScriptInfo()
assert locate_app("cliapp.app:testapp").name == "testapp" assert locate_app(script_info, "cliapp.app").name == "testapp"
assert locate_app("cliapp.multiapp:app1").name == "app1" assert locate_app(script_info, "cliapp.app:testapp").name == "testapp"
pytest.raises(NoAppException, locate_app, "notanpp.py") assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(NoAppException, locate_app,
pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") script_info, "notanpp.py")
pytest.raises(ImportError, locate_app, "cliapp.importerrorapp") pytest.raises(NoAppException, locate_app,
script_info, "cliapp/app")
pytest.raises(RuntimeError, locate_app,
script_info, "cliapp.app:notanapp")
pytest.raises(NoAppException, locate_app,
script_info, "cliapp.importerrorapp")
def test_find_default_import_path(test_apps, monkeypatch, tmpdir): def test_find_default_import_path(test_apps, monkeypatch, tmpdir):
@ -103,10 +178,13 @@ def test_get_version(test_apps, capsys):
"""Test of get_version.""" """Test of get_version."""
from flask import __version__ as flask_ver from flask import __version__ as flask_ver
from sys import version as py_ver from sys import version as py_ver
class MockCtx(object): class MockCtx(object):
resilient_parsing = False resilient_parsing = False
color = None color = None
def exit(self): return def exit(self): return
ctx = MockCtx() ctx = MockCtx()
get_version(ctx, None, "test") get_version(ctx, None, "test")
out, err = capsys.readouterr() out, err = capsys.readouterr()
@ -129,8 +207,9 @@ def test_scriptinfo(test_apps):
assert obj.load_app() == app assert obj.load_app() == app
def test_with_appcontext(): def test_with_appcontext(runner):
"""Test of with_appcontext.""" """Test of with_appcontext."""
@click.command() @click.command()
@with_appcontext @with_appcontext
def testcmd(): def testcmd():
@ -138,14 +217,14 @@ def test_with_appcontext():
obj = ScriptInfo(create_app=lambda info: Flask("testapp")) obj = ScriptInfo(create_app=lambda info: Flask("testapp"))
runner = CliRunner()
result = runner.invoke(testcmd, obj=obj) result = runner.invoke(testcmd, obj=obj)
assert result.exit_code == 0 assert result.exit_code == 0
assert result.output == 'testapp\n' assert result.output == 'testapp\n'
def test_appgroup(): def test_appgroup(runner):
"""Test of with_appcontext.""" """Test of with_appcontext."""
@click.group(cls=AppGroup) @click.group(cls=AppGroup)
def cli(): def cli():
pass pass
@ -164,7 +243,6 @@ def test_appgroup():
obj = ScriptInfo(create_app=lambda info: Flask("testappgroup")) obj = ScriptInfo(create_app=lambda info: Flask("testappgroup"))
runner = CliRunner()
result = runner.invoke(cli, ['test'], obj=obj) result = runner.invoke(cli, ['test'], obj=obj)
assert result.exit_code == 0 assert result.exit_code == 0
assert result.output == 'testappgroup\n' assert result.output == 'testappgroup\n'
@ -174,8 +252,9 @@ def test_appgroup():
assert result.output == 'testappgroup\n' assert result.output == 'testappgroup\n'
def test_flaskgroup(): def test_flaskgroup(runner):
"""Test FlaskGroup.""" """Test FlaskGroup."""
def create_app(info): def create_app(info):
return Flask("flaskgroup") return Flask("flaskgroup")
@ -187,7 +266,81 @@ def test_flaskgroup():
def test(): def test():
click.echo(current_app.name) click.echo(current_app.name)
runner = CliRunner()
result = runner.invoke(cli, ['test']) result = runner.invoke(cli, ['test'])
assert result.exit_code == 0 assert result.exit_code == 0
assert result.output == 'flaskgroup\n' assert result.output == 'flaskgroup\n'
def test_print_exceptions(runner):
"""Print the stacktrace if the CLI."""
def create_app(info):
raise Exception("oh no")
return Flask("flaskgroup")
@click.group(cls=FlaskGroup, create_app=create_app)
def cli(**params):
pass
result = runner.invoke(cli, ['--help'])
assert result.exit_code == 0
assert 'Exception: oh no' in result.output
assert 'Traceback' in result.output
class TestRoutes:
@pytest.fixture
def invoke(self, runner):
def create_app(info):
app = Flask(__name__)
app.testing = True
@app.route('/get_post/<int:x>/<int:y>', methods=['GET', 'POST'])
def yyy_get_post(x, y):
pass
@app.route('/zzz_post', methods=['POST'])
def aaa_post():
pass
return app
cli = FlaskGroup(create_app=create_app)
return partial(runner.invoke, cli)
def expect_order(self, order, output):
# skip the header and match the start of each row
for expect, line in zip(order, output.splitlines()[2:]):
# do this instead of startswith for nicer pytest output
assert line[:len(expect)] == expect
def test_simple(self, invoke):
result = invoke(['routes'])
assert result.exit_code == 0
self.expect_order(
['aaa_post', 'static', 'yyy_get_post'],
result.output
)
def test_sort(self, invoke):
default_output = invoke(['routes']).output
endpoint_output = invoke(['routes', '-s', 'endpoint']).output
assert default_output == endpoint_output
self.expect_order(
['static', 'yyy_get_post', 'aaa_post'],
invoke(['routes', '-s', 'methods']).output
)
self.expect_order(
['yyy_get_post', 'static', 'aaa_post'],
invoke(['routes', '-s', 'rule']).output
)
self.expect_order(
['aaa_post', 'yyy_get_post', 'static'],
invoke(['routes', '-s', 'match']).output
)
def test_all_methods(self, invoke):
output = invoke(['routes']).output
assert 'GET, HEAD, OPTIONS, POST' not in output
output = invoke(['routes', '--all-methods']).output
assert 'GET, HEAD, OPTIONS, POST' in output

15
tests/test_deprecations.py

@ -15,11 +15,8 @@ import flask
class TestRequestDeprecation(object): class TestRequestDeprecation(object):
def test_request_json(self, recwarn, app, client):
def test_request_json(self, recwarn):
"""Request.json is deprecated""" """Request.json is deprecated"""
app = flask.Flask(__name__)
app.testing = True
@app.route('/', methods=['POST']) @app.route('/', methods=['POST'])
def index(): def index():
@ -27,20 +24,16 @@ class TestRequestDeprecation(object):
print(flask.request.json) print(flask.request.json)
return 'OK' return 'OK'
c = app.test_client() client.post('/', data='{"spam": 42}', content_type='application/json')
c.post('/', data='{"spam": 42}', content_type='application/json')
recwarn.pop(DeprecationWarning) recwarn.pop(DeprecationWarning)
def test_request_module(self, recwarn): def test_request_module(self, recwarn, app, client):
"""Request.module is deprecated""" """Request.module is deprecated"""
app = flask.Flask(__name__)
app.testing = True
@app.route('/') @app.route('/')
def index(): def index():
assert flask.request.module is None assert flask.request.module is None
return 'OK' return 'OK'
c = app.test_client() client.get('/')
c.get('/')
recwarn.pop(DeprecationWarning) recwarn.pop(DeprecationWarning)

22
tests/test_ext.py

@ -21,19 +21,18 @@ from flask._compat import PY2
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def disable_extwarnings(request, recwarn): def disable_extwarnings(recwarn):
from flask.exthook import ExtDeprecationWarning from flask.exthook import ExtDeprecationWarning
def inner(): yield
assert set(w.category for w in recwarn.list) \
<= set([ExtDeprecationWarning])
recwarn.clear()
request.addfinalizer(inner) assert set(w.category for w in recwarn.list) \
<= set([ExtDeprecationWarning])
recwarn.clear()
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def importhook_setup(monkeypatch, request): def importhook_setup(monkeypatch):
# we clear this out for various reasons. The most important one is # we clear this out for various reasons. The most important one is
# that a real flaskext could be in there which would disable our # that a real flaskext could be in there which would disable our
# fake package. Secondly we want to make sure that the flaskext # fake package. Secondly we want to make sure that the flaskext
@ -58,12 +57,11 @@ def importhook_setup(monkeypatch, request):
import_hooks += 1 import_hooks += 1
assert import_hooks == 1 assert import_hooks == 1
def teardown(): yield
from flask import ext
for key in ext.__dict__:
assert '.' not in key
request.addfinalizer(teardown) from flask import ext
for key in ext.__dict__:
assert '.' not in key
@pytest.fixture @pytest.fixture

717
tests/test_helpers.py

File diff suppressed because it is too large Load Diff

11
tests/test_regression.py

@ -9,15 +9,14 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import pytest
import os
import gc import gc
import sys import sys
import flask
import threading import threading
import pytest
from werkzeug.exceptions import NotFound from werkzeug.exceptions import NotFound
import flask
_gc_lock = threading.Lock() _gc_lock = threading.Lock()
@ -77,11 +76,9 @@ def test_safe_join_toplevel_pardir():
safe_join('/foo', '..') safe_join('/foo', '..')
def test_aborting(): def test_aborting(app):
class Foo(Exception): class Foo(Exception):
whatever = 42 whatever = 42
app = flask.Flask(__name__)
app.testing = True
@app.errorhandler(Foo) @app.errorhandler(Foo)
def handle_foo(e): def handle_foo(e):

140
tests/test_reqctx.py

@ -12,6 +12,7 @@
import pytest import pytest
import flask import flask
from flask.sessions import SessionInterface
try: try:
from greenlet import greenlet from greenlet import greenlet
@ -19,9 +20,9 @@ except ImportError:
greenlet = None greenlet = None
def test_teardown_on_pop(): def test_teardown_on_pop(app):
buffer = [] buffer = []
app = flask.Flask(__name__)
@app.teardown_request @app.teardown_request
def end_of_request(exception): def end_of_request(exception):
buffer.append(exception) buffer.append(exception)
@ -32,9 +33,10 @@ def test_teardown_on_pop():
ctx.pop() ctx.pop()
assert buffer == [None] assert buffer == [None]
def test_teardown_with_previous_exception():
def test_teardown_with_previous_exception(app):
buffer = [] buffer = []
app = flask.Flask(__name__)
@app.teardown_request @app.teardown_request
def end_of_request(exception): def end_of_request(exception):
buffer.append(exception) buffer.append(exception)
@ -48,9 +50,10 @@ def test_teardown_with_previous_exception():
assert buffer == [] assert buffer == []
assert buffer == [None] assert buffer == [None]
def test_teardown_with_handled_exception():
def test_teardown_with_handled_exception(app):
buffer = [] buffer = []
app = flask.Flask(__name__)
@app.teardown_request @app.teardown_request
def end_of_request(exception): def end_of_request(exception):
buffer.append(exception) buffer.append(exception)
@ -63,8 +66,8 @@ def test_teardown_with_handled_exception():
pass pass
assert buffer == [None] assert buffer == [None]
def test_proper_test_request_context():
app = flask.Flask(__name__) def test_proper_test_request_context(app):
app.config.update( app.config.update(
SERVER_NAME='localhost.localdomain:5000' SERVER_NAME='localhost.localdomain:5000'
) )
@ -79,11 +82,11 @@ def test_proper_test_request_context():
with app.test_request_context('/'): with app.test_request_context('/'):
assert flask.url_for('index', _external=True) == \ assert flask.url_for('index', _external=True) == \
'http://localhost.localdomain:5000/' 'http://localhost.localdomain:5000/'
with app.test_request_context('/'): with app.test_request_context('/'):
assert flask.url_for('sub', _external=True) == \ assert flask.url_for('sub', _external=True) == \
'http://foo.localhost.localdomain:5000/' 'http://foo.localhost.localdomain:5000/'
try: try:
with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}):
@ -103,11 +106,12 @@ def test_proper_test_request_context():
with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}):
pass pass
def test_context_binding():
app = flask.Flask(__name__) def test_context_binding(app):
@app.route('/') @app.route('/')
def index(): def index():
return 'Hello %s!' % flask.request.args['name'] return 'Hello %s!' % flask.request.args['name']
@app.route('/meh') @app.route('/meh')
def meh(): def meh():
return flask.request.url return flask.request.url
@ -118,8 +122,8 @@ def test_context_binding():
assert meh() == 'http://localhost/meh' assert meh() == 'http://localhost/meh'
assert flask._request_ctx_stack.top is None assert flask._request_ctx_stack.top is None
def test_context_test():
app = flask.Flask(__name__) def test_context_test(app):
assert not flask.request assert not flask.request
assert not flask.has_request_context() assert not flask.has_request_context()
ctx = app.test_request_context() ctx = app.test_request_context()
@ -130,8 +134,8 @@ def test_context_test():
finally: finally:
ctx.pop() ctx.pop()
def test_manual_context_binding():
app = flask.Flask(__name__) def test_manual_context_binding(app):
@app.route('/') @app.route('/')
def index(): def index():
return 'Hello %s!' % flask.request.args['name'] return 'Hello %s!' % flask.request.args['name']
@ -143,53 +147,81 @@ def test_manual_context_binding():
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
index() index()
@pytest.mark.skipif(greenlet is None, reason='greenlet not installed') @pytest.mark.skipif(greenlet is None, reason='greenlet not installed')
def test_greenlet_context_copying(): class TestGreenletContextCopying(object):
app = flask.Flask(__name__)
greenlets = []
@app.route('/') def test_greenlet_context_copying(self, app, client):
def index(): greenlets = []
reqctx = flask._request_ctx_stack.top.copy()
def g(): @app.route('/')
assert not flask.request def index():
assert not flask.current_app reqctx = flask._request_ctx_stack.top.copy()
with reqctx:
def g():
assert not flask.request
assert not flask.current_app
with reqctx:
assert flask.request
assert flask.current_app == app
assert flask.request.path == '/'
assert flask.request.args['foo'] == 'bar'
assert not flask.request
return 42
greenlets.append(greenlet(g))
return 'Hello World!'
rv = client.get('/?foo=bar')
assert rv.data == b'Hello World!'
result = greenlets[0].run()
assert result == 42
def test_greenlet_context_copying_api(self, app, client):
greenlets = []
@app.route('/')
def index():
reqctx = flask._request_ctx_stack.top.copy()
@flask.copy_current_request_context
def g():
assert flask.request assert flask.request
assert flask.current_app == app assert flask.current_app == app
assert flask.request.path == '/' assert flask.request.path == '/'
assert flask.request.args['foo'] == 'bar' assert flask.request.args['foo'] == 'bar'
assert not flask.request return 42
return 42
greenlets.append(greenlet(g))
return 'Hello World!'
rv = app.test_client().get('/?foo=bar') greenlets.append(greenlet(g))
assert rv.data == b'Hello World!' return 'Hello World!'
result = greenlets[0].run() rv = client.get('/?foo=bar')
assert result == 42 assert rv.data == b'Hello World!'
result = greenlets[0].run()
assert result == 42
@pytest.mark.skipif(greenlet is None, reason='greenlet not installed')
def test_greenlet_context_copying_api(): def test_session_error_pops_context():
app = flask.Flask(__name__) class SessionError(Exception):
greenlets = [] pass
class FailingSessionInterface(SessionInterface):
def open_session(self, app, request):
raise SessionError()
class CustomFlask(flask.Flask):
session_interface = FailingSessionInterface()
app = CustomFlask(__name__)
@app.route('/') @app.route('/')
def index(): def index():
reqctx = flask._request_ctx_stack.top.copy() # shouldn't get here
@flask.copy_current_request_context assert False
def g():
assert flask.request response = app.test_client().get('/')
assert flask.current_app == app assert response.status_code == 500
assert flask.request.path == '/' assert not flask.request
assert flask.request.args['foo'] == 'bar' assert not flask.current_app
return 42
greenlets.append(greenlet(g))
return 'Hello World!'
rv = app.test_client().get('/?foo=bar')
assert rv.data == b'Hello World!'
result = greenlets[0].run()
assert result == 42

12
tests/test_signals.py

@ -18,15 +18,13 @@ except ImportError:
import flask import flask
pytestmark = pytest.mark.skipif( pytestmark = pytest.mark.skipif(
blinker is None, blinker is None,
reason='Signals require the blinker library.' reason='Signals require the blinker library.'
) )
def test_template_rendered():
app = flask.Flask(__name__)
def test_template_rendered(app, client):
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('simple_template.html', whiskey=42) return flask.render_template('simple_template.html', whiskey=42)
@ -38,7 +36,7 @@ def test_template_rendered():
flask.template_rendered.connect(record, app) flask.template_rendered.connect(record, app)
try: try:
app.test_client().get('/') client.get('/')
assert len(recorded) == 1 assert len(recorded) == 1
template, context = recorded[0] template, context = recorded[0]
assert template.name == 'simple_template.html' assert template.name == 'simple_template.html'
@ -46,6 +44,7 @@ def test_template_rendered():
finally: finally:
flask.template_rendered.disconnect(record, app) flask.template_rendered.disconnect(record, app)
def test_before_render_template(): def test_before_render_template():
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -70,6 +69,7 @@ def test_before_render_template():
finally: finally:
flask.before_render_template.disconnect(record, app) flask.before_render_template.disconnect(record, app)
def test_request_signals(): def test_request_signals():
app = flask.Flask(__name__) app = flask.Flask(__name__)
calls = [] calls = []
@ -109,6 +109,7 @@ def test_request_signals():
flask.request_started.disconnect(before_request_signal, app) flask.request_started.disconnect(before_request_signal, app)
flask.request_finished.disconnect(after_request_signal, app) flask.request_finished.disconnect(after_request_signal, app)
def test_request_exception_signal(): def test_request_exception_signal():
app = flask.Flask(__name__) app = flask.Flask(__name__)
recorded = [] recorded = []
@ -128,6 +129,7 @@ def test_request_exception_signal():
finally: finally:
flask.got_request_exception.disconnect(record, app) flask.got_request_exception.disconnect(record, app)
def test_appcontext_signals(): def test_appcontext_signals():
app = flask.Flask(__name__) app = flask.Flask(__name__)
recorded = [] recorded = []
@ -154,6 +156,7 @@ def test_appcontext_signals():
flask.appcontext_pushed.disconnect(record_push, app) flask.appcontext_pushed.disconnect(record_push, app)
flask.appcontext_popped.disconnect(record_pop, app) flask.appcontext_popped.disconnect(record_pop, app)
def test_flash_signal(): def test_flash_signal():
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.config['SECRET_KEY'] = 'secret' app.config['SECRET_KEY'] = 'secret'
@ -180,6 +183,7 @@ def test_flash_signal():
finally: finally:
flask.message_flashed.disconnect(record, app) flask.message_flashed.disconnect(record, app)
def test_appcontext_tearing_down_signal(): def test_appcontext_tearing_down_signal():
app = flask.Flask(__name__) app = flask.Flask(__name__)
recorded = [] recorded = []

214
tests/test_templating.py

@ -16,40 +16,43 @@ import logging
from jinja2 import TemplateNotFound from jinja2 import TemplateNotFound
def test_context_processing(): def test_context_processing(app, client):
app = flask.Flask(__name__)
@app.context_processor @app.context_processor
def context_processor(): def context_processor():
return {'injected_value': 42} return {'injected_value': 42}
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('context_template.html', value=23) return flask.render_template('context_template.html', value=23)
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'<p>23|42' assert rv.data == b'<p>23|42'
def test_original_win():
app = flask.Flask(__name__) def test_original_win(app, client):
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template_string('{{ config }}', config=42) return flask.render_template_string('{{ config }}', config=42)
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'42' assert rv.data == b'42'
def test_request_less_rendering():
app = flask.Flask(__name__) def test_request_less_rendering(app, app_ctx):
app.config['WORLD_NAME'] = 'Special World' app.config['WORLD_NAME'] = 'Special World'
@app.context_processor @app.context_processor
def context_processor(): def context_processor():
return dict(foo=42) return dict(foo=42)
with app.app_context(): rv = flask.render_template_string('Hello {{ config.WORLD_NAME }} '
rv = flask.render_template_string('Hello {{ config.WORLD_NAME }} ' '{{ foo }}')
'{{ foo }}') assert rv == 'Hello Special World 42'
assert rv == 'Hello Special World 42'
def test_standard_context():
app = flask.Flask(__name__) def test_standard_context(app, client):
app.secret_key = 'development key' app.secret_key = 'development key'
@app.route('/') @app.route('/')
def index(): def index():
flask.g.foo = 23 flask.g.foo = 23
@ -60,17 +63,20 @@ def test_standard_context():
{{ config.DEBUG }} {{ config.DEBUG }}
{{ session.test }} {{ session.test }}
''') ''')
rv = app.test_client().get('/?foo=42')
rv = client.get('/?foo=42')
assert rv.data.split() == [b'42', b'23', b'False', b'aha'] assert rv.data.split() == [b'42', b'23', b'False', b'aha']
def test_escaping():
def test_escaping(app, client):
text = '<p>Hello World!' text = '<p>Hello World!'
app = flask.Flask(__name__)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('escaping_template.html', text=text, return flask.render_template('escaping_template.html', text=text,
html=flask.Markup(text)) html=flask.Markup(text))
lines = app.test_client().get('/').data.splitlines()
lines = client.get('/').data.splitlines()
assert lines == [ assert lines == [
b'&lt;p&gt;Hello World!', b'&lt;p&gt;Hello World!',
b'<p>Hello World!', b'<p>Hello World!',
@ -80,14 +86,16 @@ def test_escaping():
b'<p>Hello World!' b'<p>Hello World!'
] ]
def test_no_escaping():
def test_no_escaping(app, client):
text = '<p>Hello World!' text = '<p>Hello World!'
app = flask.Flask(__name__)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('non_escaping_template.txt', text=text, return flask.render_template('non_escaping_template.txt', text=text,
html=flask.Markup(text)) html=flask.Markup(text))
lines = app.test_client().get('/').data.splitlines()
lines = client.get('/').data.splitlines()
assert lines == [ assert lines == [
b'<p>Hello World!', b'<p>Hello World!',
b'<p>Hello World!', b'<p>Hello World!',
@ -99,224 +107,255 @@ def test_no_escaping():
b'<p>Hello World!' b'<p>Hello World!'
] ]
def test_escaping_without_template_filename():
app = flask.Flask(__name__)
with app.test_request_context():
assert flask.render_template_string(
'{{ foo }}', foo='<test>') == '&lt;test&gt;'
assert flask.render_template('mail.txt', foo='<test>') == \
'<test> Mail'
def test_macros(): def test_escaping_without_template_filename(app, client, req_ctx):
app = flask.Flask(__name__) assert flask.render_template_string(
with app.test_request_context(): '{{ foo }}', foo='<test>') == '&lt;test&gt;'
macro = flask.get_template_attribute('_macro.html', 'hello') assert flask.render_template('mail.txt', foo='<test>') == '<test> Mail'
assert macro('World') == 'Hello World!'
def test_template_filter():
app = flask.Flask(__name__) def test_macros(app, req_ctx):
macro = flask.get_template_attribute('_macro.html', 'hello')
assert macro('World') == 'Hello World!'
def test_template_filter(app):
@app.template_filter() @app.template_filter()
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
assert 'my_reverse' in app.jinja_env.filters.keys() assert 'my_reverse' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['my_reverse'] == my_reverse assert app.jinja_env.filters['my_reverse'] == my_reverse
assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba'
def test_add_template_filter():
app = flask.Flask(__name__) def test_add_template_filter(app):
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
app.add_template_filter(my_reverse) app.add_template_filter(my_reverse)
assert 'my_reverse' in app.jinja_env.filters.keys() assert 'my_reverse' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['my_reverse'] == my_reverse assert app.jinja_env.filters['my_reverse'] == my_reverse
assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba'
def test_template_filter_with_name():
app = flask.Flask(__name__) def test_template_filter_with_name(app):
@app.template_filter('strrev') @app.template_filter('strrev')
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
assert 'strrev' in app.jinja_env.filters.keys() assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse assert app.jinja_env.filters['strrev'] == my_reverse
assert app.jinja_env.filters['strrev']('abcd') == 'dcba' assert app.jinja_env.filters['strrev']('abcd') == 'dcba'
def test_add_template_filter_with_name():
app = flask.Flask(__name__) def test_add_template_filter_with_name(app):
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
app.add_template_filter(my_reverse, 'strrev') app.add_template_filter(my_reverse, 'strrev')
assert 'strrev' in app.jinja_env.filters.keys() assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse assert app.jinja_env.filters['strrev'] == my_reverse
assert app.jinja_env.filters['strrev']('abcd') == 'dcba' assert app.jinja_env.filters['strrev']('abcd') == 'dcba'
def test_template_filter_with_template():
app = flask.Flask(__name__) def test_template_filter_with_template(app, client):
@app.template_filter() @app.template_filter()
def super_reverse(s): def super_reverse(s):
return s[::-1] return s[::-1]
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_add_template_filter_with_template():
app = flask.Flask(__name__) def test_add_template_filter_with_template(app, client):
def super_reverse(s): def super_reverse(s):
return s[::-1] return s[::-1]
app.add_template_filter(super_reverse) app.add_template_filter(super_reverse)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_template_filter_with_name_and_template():
app = flask.Flask(__name__) def test_template_filter_with_name_and_template(app, client):
@app.template_filter('super_reverse') @app.template_filter('super_reverse')
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_add_template_filter_with_name_and_template():
app = flask.Flask(__name__) def test_add_template_filter_with_name_and_template(app, client):
def my_reverse(s): def my_reverse(s):
return s[::-1] return s[::-1]
app.add_template_filter(my_reverse, 'super_reverse') app.add_template_filter(my_reverse, 'super_reverse')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_filter.html', value='abcd') return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba' assert rv.data == b'dcba'
def test_template_test():
app = flask.Flask(__name__) def test_template_test(app):
@app.template_test() @app.template_test()
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
assert 'boolean' in app.jinja_env.tests.keys() assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == boolean assert app.jinja_env.tests['boolean'] == boolean
assert app.jinja_env.tests['boolean'](False) assert app.jinja_env.tests['boolean'](False)
def test_add_template_test():
app = flask.Flask(__name__) def test_add_template_test(app):
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app.add_template_test(boolean) app.add_template_test(boolean)
assert 'boolean' in app.jinja_env.tests.keys() assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == boolean assert app.jinja_env.tests['boolean'] == boolean
assert app.jinja_env.tests['boolean'](False) assert app.jinja_env.tests['boolean'](False)
def test_template_test_with_name():
app = flask.Flask(__name__) def test_template_test_with_name(app):
@app.template_test('boolean') @app.template_test('boolean')
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
assert 'boolean' in app.jinja_env.tests.keys() assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean assert app.jinja_env.tests['boolean'] == is_boolean
assert app.jinja_env.tests['boolean'](False) assert app.jinja_env.tests['boolean'](False)
def test_add_template_test_with_name():
app = flask.Flask(__name__) def test_add_template_test_with_name(app):
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app.add_template_test(is_boolean, 'boolean') app.add_template_test(is_boolean, 'boolean')
assert 'boolean' in app.jinja_env.tests.keys() assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean assert app.jinja_env.tests['boolean'] == is_boolean
assert app.jinja_env.tests['boolean'](False) assert app.jinja_env.tests['boolean'](False)
def test_template_test_with_template():
app = flask.Flask(__name__) def test_template_test_with_template(app, client):
@app.template_test() @app.template_test()
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_add_template_test_with_template():
app = flask.Flask(__name__) def test_add_template_test_with_template(app, client):
def boolean(value): def boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app.add_template_test(boolean) app.add_template_test(boolean)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_template_test_with_name_and_template():
app = flask.Flask(__name__) def test_template_test_with_name_and_template(app, client):
@app.template_test('boolean') @app.template_test('boolean')
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_add_template_test_with_name_and_template():
app = flask.Flask(__name__) def test_add_template_test_with_name_and_template(app, client):
def is_boolean(value): def is_boolean(value):
return isinstance(value, bool) return isinstance(value, bool)
app.add_template_test(is_boolean, 'boolean') app.add_template_test(is_boolean, 'boolean')
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('template_test.html', value=False) return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
assert b'Success!' in rv.data assert b'Success!' in rv.data
def test_add_template_global():
app = flask.Flask(__name__) def test_add_template_global(app, app_ctx):
@app.template_global() @app.template_global()
def get_stuff(): def get_stuff():
return 42 return 42
assert 'get_stuff' in app.jinja_env.globals.keys() assert 'get_stuff' in app.jinja_env.globals.keys()
assert app.jinja_env.globals['get_stuff'] == get_stuff assert app.jinja_env.globals['get_stuff'] == get_stuff
assert app.jinja_env.globals['get_stuff'](), 42 assert app.jinja_env.globals['get_stuff'](), 42
with app.app_context():
rv = flask.render_template_string('{{ get_stuff() }}')
assert rv == '42'
def test_custom_template_loader(): rv = flask.render_template_string('{{ get_stuff() }}')
assert rv == '42'
def test_custom_template_loader(client):
class MyFlask(flask.Flask): class MyFlask(flask.Flask):
def create_global_jinja_loader(self): def create_global_jinja_loader(self):
from jinja2 import DictLoader from jinja2 import DictLoader
return DictLoader({'index.html': 'Hello Custom World!'}) return DictLoader({'index.html': 'Hello Custom World!'})
app = MyFlask(__name__) app = MyFlask(__name__)
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template('index.html') return flask.render_template('index.html')
c = app.test_client() c = app.test_client()
rv = c.get('/') rv = c.get('/')
assert rv.data == b'Hello Custom World!' assert rv.data == b'Hello Custom World!'
def test_iterable_loader():
app = flask.Flask(__name__) def test_iterable_loader(app, client):
@app.context_processor @app.context_processor
def context_processor(): def context_processor():
return {'whiskey': 'Jameson'} return {'whiskey': 'Jameson'}
@app.route('/') @app.route('/')
def index(): def index():
return flask.render_template( return flask.render_template(
['no_template.xml', # should skip this one ['no_template.xml', # should skip this one
'simple_template.html', # should render this 'simple_template.html', # should render this
'context_template.html'], 'context_template.html'],
value=23) value=23)
rv = app.test_client().get('/') rv = client.get('/')
assert rv.data == b'<h1>Jameson</h1>' assert rv.data == b'<h1>Jameson</h1>'
def test_templates_auto_reload():
def test_templates_auto_reload(app):
# debug is False, config option is None # debug is False, config option is None
app = flask.Flask(__name__)
assert app.debug is False assert app.debug is False
assert app.config['TEMPLATES_AUTO_RELOAD'] is None assert app.config['TEMPLATES_AUTO_RELOAD'] is None
assert app.jinja_env.auto_reload is False assert app.jinja_env.auto_reload is False
@ -346,10 +385,12 @@ def test_templates_auto_reload():
app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['TEMPLATES_AUTO_RELOAD'] = True
assert app.jinja_env.auto_reload is True assert app.jinja_env.auto_reload is True
def test_template_loader_debugging(test_apps): def test_template_loader_debugging(test_apps):
from blueprintapp import app from blueprintapp import app
called = [] called = []
class _TestHandler(logging.Handler): class _TestHandler(logging.Handler):
def handle(x, record): def handle(x, record):
called.append(True) called.append(True)
@ -381,6 +422,7 @@ def test_template_loader_debugging(test_apps):
assert len(called) == 1 assert len(called) == 1
def test_custom_jinja_env(): def test_custom_jinja_env():
class CustomEnvironment(flask.templating.Environment): class CustomEnvironment(flask.templating.Environment):
pass pass

176
tests/test_testing.py

@ -16,70 +16,64 @@ import werkzeug
from flask._compat import text_type from flask._compat import text_type
def test_environ_defaults_from_config(): def test_environ_defaults_from_config(app, client):
app = flask.Flask(__name__)
app.testing = True
app.config['SERVER_NAME'] = 'example.com:1234' app.config['SERVER_NAME'] = 'example.com:1234'
app.config['APPLICATION_ROOT'] = '/foo' app.config['APPLICATION_ROOT'] = '/foo'
@app.route('/') @app.route('/')
def index(): def index():
return flask.request.url return flask.request.url
ctx = app.test_request_context() ctx = app.test_request_context()
assert ctx.request.url == 'http://example.com:1234/foo/' assert ctx.request.url == 'http://example.com:1234/foo/'
with app.test_client() as c:
rv = c.get('/')
assert rv.data == b'http://example.com:1234/foo/'
def test_environ_defaults(): rv = client.get('/')
app = flask.Flask(__name__) assert rv.data == b'http://example.com:1234/foo/'
app.testing = True
def test_environ_defaults(app, client, app_ctx, req_ctx):
@app.route('/') @app.route('/')
def index(): def index():
return flask.request.url return flask.request.url
ctx = app.test_request_context() ctx = app.test_request_context()
assert ctx.request.url == 'http://localhost/' assert ctx.request.url == 'http://localhost/'
with app.test_client() as c: with client:
rv = c.get('/') rv = client.get('/')
assert rv.data == b'http://localhost/' assert rv.data == b'http://localhost/'
def test_environ_base_default():
app = flask.Flask(__name__) def test_environ_base_default(app, client, app_ctx):
app.testing = True
@app.route('/') @app.route('/')
def index(): def index():
flask.g.user_agent = flask.request.headers["User-Agent"] flask.g.user_agent = flask.request.headers["User-Agent"]
return flask.request.remote_addr return flask.request.remote_addr
with app.test_client() as c: rv = client.get('/')
rv = c.get('/') assert rv.data == b'127.0.0.1'
assert rv.data == b'127.0.0.1' assert flask.g.user_agent == 'werkzeug/' + werkzeug.__version__
assert flask.g.user_agent == 'werkzeug/' + werkzeug.__version__
def test_environ_base_modified():
app = flask.Flask(__name__) def test_environ_base_modified(app, client, app_ctx):
app.testing = True
@app.route('/') @app.route('/')
def index(): def index():
flask.g.user_agent = flask.request.headers["User-Agent"] flask.g.user_agent = flask.request.headers["User-Agent"]
return flask.request.remote_addr return flask.request.remote_addr
with app.test_client() as c: client.environ_base['REMOTE_ADDR'] = '0.0.0.0'
c.environ_base['REMOTE_ADDR'] = '0.0.0.0' client.environ_base['HTTP_USER_AGENT'] = 'Foo'
c.environ_base['HTTP_USER_AGENT'] = 'Foo' rv = client.get('/')
rv = c.get('/') assert rv.data == b'0.0.0.0'
assert rv.data == b'0.0.0.0' assert flask.g.user_agent == 'Foo'
assert flask.g.user_agent == 'Foo'
client.environ_base['REMOTE_ADDR'] = '0.0.0.1'
c.environ_base['REMOTE_ADDR'] = '0.0.0.1' client.environ_base['HTTP_USER_AGENT'] = 'Bar'
c.environ_base['HTTP_USER_AGENT'] = 'Bar' rv = client.get('/')
rv = c.get('/') assert rv.data == b'0.0.0.1'
assert rv.data == b'0.0.0.1' assert flask.g.user_agent == 'Bar'
assert flask.g.user_agent == 'Bar'
def test_redirect_keep_session(): def test_redirect_keep_session(app, client, app_ctx):
app = flask.Flask(__name__)
app.secret_key = 'testing' app.secret_key = 'testing'
@app.route('/', methods=['GET', 'POST']) @app.route('/', methods=['GET', 'POST'])
@ -93,43 +87,43 @@ def test_redirect_keep_session():
def get_session(): def get_session():
return flask.session.get('data', '<missing>') return flask.session.get('data', '<missing>')
with app.test_client() as c: with client:
rv = c.get('/getsession') rv = client.get('/getsession')
assert rv.data == b'<missing>' assert rv.data == b'<missing>'
rv = c.get('/') rv = client.get('/')
assert rv.data == b'index' assert rv.data == b'index'
assert flask.session.get('data') == 'foo' assert flask.session.get('data') == 'foo'
rv = c.post('/', data={}, follow_redirects=True) rv = client.post('/', data={}, follow_redirects=True)
assert rv.data == b'foo' assert rv.data == b'foo'
# This support requires a new Werkzeug version # This support requires a new Werkzeug version
if not hasattr(c, 'redirect_client'): if not hasattr(client, 'redirect_client'):
assert flask.session.get('data') == 'foo' assert flask.session.get('data') == 'foo'
rv = c.get('/getsession') rv = client.get('/getsession')
assert rv.data == b'foo' assert rv.data == b'foo'
def test_session_transactions():
app = flask.Flask(__name__) def test_session_transactions(app, client):
app.testing = True
app.secret_key = 'testing' app.secret_key = 'testing'
@app.route('/') @app.route('/')
def index(): def index():
return text_type(flask.session['foo']) return text_type(flask.session['foo'])
with app.test_client() as c: with client:
with c.session_transaction() as sess: with client.session_transaction() as sess:
assert len(sess) == 0 assert len(sess) == 0
sess['foo'] = [42] sess['foo'] = [42]
assert len(sess) == 1 assert len(sess) == 1
rv = c.get('/') rv = client.get('/')
assert rv.data == b'[42]' assert rv.data == b'[42]'
with c.session_transaction() as sess: with client.session_transaction() as sess:
assert len(sess) == 1 assert len(sess) == 1
assert sess['foo'] == [42] assert sess['foo'] == [42]
def test_session_transactions_no_null_sessions(): def test_session_transactions_no_null_sessions():
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.testing = True app.testing = True
@ -140,30 +134,29 @@ def test_session_transactions_no_null_sessions():
pass pass
assert 'Session backend did not open a session' in str(e.value) assert 'Session backend did not open a session' in str(e.value)
def test_session_transactions_keep_context():
app = flask.Flask(__name__) def test_session_transactions_keep_context(app, client, req_ctx):
app.testing = True
app.secret_key = 'testing' app.secret_key = 'testing'
with app.test_client() as c: rv = client.get('/')
rv = c.get('/') req = flask.request._get_current_object()
req = flask.request._get_current_object() assert req is not None
assert req is not None with client.session_transaction():
with c.session_transaction(): assert req is flask.request._get_current_object()
assert req is flask.request._get_current_object()
def test_session_transaction_needs_cookies():
app = flask.Flask(__name__) def test_session_transaction_needs_cookies(app):
app.testing = True
c = app.test_client(use_cookies=False) c = app.test_client(use_cookies=False)
with pytest.raises(RuntimeError) as e: with pytest.raises(RuntimeError) as e:
with c.session_transaction() as s: with c.session_transaction() as s:
pass pass
assert 'cookies' in str(e.value) assert 'cookies' in str(e.value)
def test_test_client_context_binding():
app = flask.Flask(__name__) def test_test_client_context_binding(app, client):
app.config['LOGGER_HANDLER_POLICY'] = 'never' app.config['LOGGER_HANDLER_POLICY'] = 'never'
app.testing = False
@app.route('/') @app.route('/')
def index(): def index():
flask.g.value = 42 flask.g.value = 42
@ -173,13 +166,13 @@ def test_test_client_context_binding():
def other(): def other():
1 // 0 1 // 0
with app.test_client() as c: with client:
resp = c.get('/') resp = client.get('/')
assert flask.g.value == 42 assert flask.g.value == 42
assert resp.data == b'Hello World!' assert resp.data == b'Hello World!'
assert resp.status_code == 200 assert resp.status_code == 200
resp = c.get('/other') resp = client.get('/other')
assert not hasattr(flask.g, 'value') assert not hasattr(flask.g, 'value')
assert b'Internal Server Error' in resp.data assert b'Internal Server Error' in resp.data
assert resp.status_code == 500 assert resp.status_code == 500
@ -192,55 +185,55 @@ def test_test_client_context_binding():
else: else:
raise AssertionError('some kind of exception expected') raise AssertionError('some kind of exception expected')
def test_reuse_client():
app = flask.Flask(__name__) def test_reuse_client(client):
c = app.test_client() c = client
with c: with c:
assert c.get('/').status_code == 404 assert client.get('/').status_code == 404
with c: with c:
assert c.get('/').status_code == 404 assert client.get('/').status_code == 404
def test_test_client_calls_teardown_handlers():
app = flask.Flask(__name__) def test_test_client_calls_teardown_handlers(app, client):
called = [] called = []
@app.teardown_request @app.teardown_request
def remember(error): def remember(error):
called.append(error) called.append(error)
with app.test_client() as c: with client:
assert called == [] assert called == []
c.get('/') client.get('/')
assert called == [] assert called == []
assert called == [None] assert called == [None]
del called[:] del called[:]
with app.test_client() as c: with client:
assert called == [] assert called == []
c.get('/') client.get('/')
assert called == [] assert called == []
c.get('/') client.get('/')
assert called == [None] assert called == [None]
assert called == [None, None] assert called == [None, None]
def test_full_url_request():
app = flask.Flask(__name__)
app.testing = True
def test_full_url_request(app, client):
@app.route('/action', methods=['POST']) @app.route('/action', methods=['POST'])
def action(): def action():
return 'x' return 'x'
with app.test_client() as c: with client:
rv = c.post('http://domain.com/action?vodka=42', data={'gin': 43}) rv = client.post('http://domain.com/action?vodka=42', data={'gin': 43})
assert rv.status_code == 200 assert rv.status_code == 200
assert 'gin' in flask.request.form assert 'gin' in flask.request.form
assert 'vodka' in flask.request.args assert 'vodka' in flask.request.args
def test_subdomain():
app = flask.Flask(__name__) def test_subdomain(app, client):
app.config['SERVER_NAME'] = 'example.com' app.config['SERVER_NAME'] = 'example.com'
@app.route('/', subdomain='<company_id>') @app.route('/', subdomain='<company_id>')
def view(company_id): def view(company_id):
return company_id return company_id
@ -248,15 +241,16 @@ def test_subdomain():
with app.test_request_context(): with app.test_request_context():
url = flask.url_for('view', company_id='xxx') url = flask.url_for('view', company_id='xxx')
with app.test_client() as c: with client:
response = c.get(url) response = client.get(url)
assert 200 == response.status_code assert 200 == response.status_code
assert b'xxx' == response.data assert b'xxx' == response.data
def test_nosubdomain():
app = flask.Flask(__name__) def test_nosubdomain(app, client):
app.config['SERVER_NAME'] = 'example.com' app.config['SERVER_NAME'] = 'example.com'
@app.route('/<company_id>') @app.route('/<company_id>')
def view(company_id): def view(company_id):
return company_id return company_id
@ -264,8 +258,8 @@ def test_nosubdomain():
with app.test_request_context(): with app.test_request_context():
url = flask.url_for('view', company_id='xxx') url = flask.url_for('view', company_id='xxx')
with app.test_client() as c: with client:
response = c.get(url) response = client.get(url)
assert 200 == response.status_code assert 200 == response.status_code
assert b'xxx' == response.data assert b'xxx' == response.data

79
tests/test_user_error_handler.py

@ -1,10 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from werkzeug.exceptions import Forbidden, InternalServerError from werkzeug.exceptions import (
Forbidden,
InternalServerError,
HTTPException,
NotFound
)
import flask import flask
def test_error_handler_no_match(): def test_error_handler_no_match(app, client):
app = flask.Flask(__name__)
class CustomException(Exception): class CustomException(Exception):
pass pass
@ -26,15 +30,12 @@ def test_error_handler_no_match():
def key_error(): def key_error():
raise KeyError() raise KeyError()
c = app.test_client() app.testing = False
assert client.get('/custom').data == b'custom'
assert c.get('/custom').data == b'custom' assert client.get('/keyerror').data == b'KeyError'
assert c.get('/keyerror').data == b'KeyError'
def test_error_handler_subclass(): def test_error_handler_subclass(app):
app = flask.Flask(__name__)
class ParentException(Exception): class ParentException(Exception):
pass pass
@ -73,9 +74,7 @@ def test_error_handler_subclass():
assert c.get('/child-registered').data == b'child-registered' assert c.get('/child-registered').data == b'child-registered'
def test_error_handler_http_subclass(): def test_error_handler_http_subclass(app):
app = flask.Flask(__name__)
class ForbiddenSubclassRegistered(Forbidden): class ForbiddenSubclassRegistered(Forbidden):
pass pass
@ -111,7 +110,7 @@ def test_error_handler_http_subclass():
assert c.get('/forbidden-registered').data == b'forbidden-registered' assert c.get('/forbidden-registered').data == b'forbidden-registered'
def test_error_handler_blueprint(): def test_error_handler_blueprint(app):
bp = flask.Blueprint('bp', __name__) bp = flask.Blueprint('bp', __name__)
@bp.errorhandler(500) @bp.errorhandler(500)
@ -122,8 +121,6 @@ def test_error_handler_blueprint():
def bp_test(): def bp_test():
raise InternalServerError() raise InternalServerError()
app = flask.Flask(__name__)
@app.errorhandler(500) @app.errorhandler(500)
def app_exception_handler(e): def app_exception_handler(e):
return 'app-error' return 'app-error'
@ -138,3 +135,53 @@ def test_error_handler_blueprint():
assert c.get('/error').data == b'app-error' assert c.get('/error').data == b'app-error'
assert c.get('/bp/error').data == b'bp-error' assert c.get('/bp/error').data == b'bp-error'
def test_default_error_handler():
bp = flask.Blueprint('bp', __name__)
@bp.errorhandler(HTTPException)
def bp_exception_handler(e):
assert isinstance(e, HTTPException)
assert isinstance(e, NotFound)
return 'bp-default'
@bp.errorhandler(Forbidden)
def bp_exception_handler(e):
assert isinstance(e, Forbidden)
return 'bp-forbidden'
@bp.route('/undefined')
def bp_registered_test():
raise NotFound()
@bp.route('/forbidden')
def bp_forbidden_test():
raise Forbidden()
app = flask.Flask(__name__)
@app.errorhandler(HTTPException)
def catchall_errorhandler(e):
assert isinstance(e, HTTPException)
assert isinstance(e, NotFound)
return 'default'
@app.errorhandler(Forbidden)
def catchall_errorhandler(e):
assert isinstance(e, Forbidden)
return 'forbidden'
@app.route('/forbidden')
def forbidden():
raise Forbidden()
app.register_blueprint(bp, url_prefix='/bp')
c = app.test_client()
assert c.get('/bp/undefined').data == b'bp-default'
assert c.get('/bp/forbidden').data == b'bp-forbidden'
assert c.get('/undefined').data == b'default'
assert c.get('/forbidden').data == b'forbidden'

127
tests/test_views.py

@ -16,6 +16,7 @@ import flask.views
from werkzeug.http import parse_set_header from werkzeug.http import parse_set_header
def common_test(app): def common_test(app):
c = app.test_client() c = app.test_client()
@ -25,23 +26,23 @@ def common_test(app):
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
assert sorted(meths) == ['GET', 'HEAD', 'OPTIONS', 'POST'] assert sorted(meths) == ['GET', 'HEAD', 'OPTIONS', 'POST']
def test_basic_view():
app = flask.Flask(__name__)
def test_basic_view(app):
class Index(flask.views.View): class Index(flask.views.View):
methods = ['GET', 'POST'] methods = ['GET', 'POST']
def dispatch_request(self): def dispatch_request(self):
return flask.request.method return flask.request.method
app.add_url_rule('/', view_func=Index.as_view('index')) app.add_url_rule('/', view_func=Index.as_view('index'))
common_test(app) common_test(app)
def test_method_based_view():
app = flask.Flask(__name__)
def test_method_based_view(app):
class Index(flask.views.MethodView): class Index(flask.views.MethodView):
def get(self): def get(self):
return 'GET' return 'GET'
def post(self): def post(self):
return 'POST' return 'POST'
@ -49,18 +50,19 @@ def test_method_based_view():
common_test(app) common_test(app)
def test_view_patching():
app = flask.Flask(__name__)
def test_view_patching(app):
class Index(flask.views.MethodView): class Index(flask.views.MethodView):
def get(self): def get(self):
1 // 0 1 // 0
def post(self): def post(self):
1 // 0 1 // 0
class Other(Index): class Other(Index):
def get(self): def get(self):
return 'GET' return 'GET'
def post(self): def post(self):
return 'POST' return 'POST'
@ -69,12 +71,12 @@ def test_view_patching():
app.add_url_rule('/', view_func=view) app.add_url_rule('/', view_func=view)
common_test(app) common_test(app)
def test_view_inheritance():
app = flask.Flask(__name__)
def test_view_inheritance(app, client):
class Index(flask.views.MethodView): class Index(flask.views.MethodView):
def get(self): def get(self):
return 'GET' return 'GET'
def post(self): def post(self):
return 'POST' return 'POST'
@ -83,35 +85,73 @@ def test_view_inheritance():
return 'DELETE' return 'DELETE'
app.add_url_rule('/', view_func=BetterIndex.as_view('index')) app.add_url_rule('/', view_func=BetterIndex.as_view('index'))
c = app.test_client()
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) meths = parse_set_header(client.open('/', method='OPTIONS').headers['Allow'])
assert sorted(meths) == ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST'] assert sorted(meths) == ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']
def test_view_decorators():
app = flask.Flask(__name__)
def test_view_decorators(app, client):
def add_x_parachute(f): def add_x_parachute(f):
def new_function(*args, **kwargs): def new_function(*args, **kwargs):
resp = flask.make_response(f(*args, **kwargs)) resp = flask.make_response(f(*args, **kwargs))
resp.headers['X-Parachute'] = 'awesome' resp.headers['X-Parachute'] = 'awesome'
return resp return resp
return new_function return new_function
class Index(flask.views.View): class Index(flask.views.View):
decorators = [add_x_parachute] decorators = [add_x_parachute]
def dispatch_request(self): def dispatch_request(self):
return 'Awesome' return 'Awesome'
app.add_url_rule('/', view_func=Index.as_view('index')) app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client() rv = client.get('/')
rv = c.get('/')
assert rv.headers['X-Parachute'] == 'awesome' assert rv.headers['X-Parachute'] == 'awesome'
assert rv.data == b'Awesome' assert rv.data == b'Awesome'
def test_implicit_head():
def test_view_provide_automatic_options_attr():
app = flask.Flask(__name__)
class Index1(flask.views.View):
provide_automatic_options = False
def dispatch_request(self):
return 'Hello World!'
app.add_url_rule('/', view_func=Index1.as_view('index'))
c = app.test_client()
rv = c.open('/', method='OPTIONS')
assert rv.status_code == 405
app = flask.Flask(__name__) app = flask.Flask(__name__)
class Index2(flask.views.View):
methods = ['OPTIONS']
provide_automatic_options = True
def dispatch_request(self):
return 'Hello World!'
app.add_url_rule('/', view_func=Index2.as_view('index'))
c = app.test_client()
rv = c.open('/', method='OPTIONS')
assert sorted(rv.allow) == ['OPTIONS']
app = flask.Flask(__name__)
class Index3(flask.views.View):
def dispatch_request(self):
return 'Hello World!'
app.add_url_rule('/', view_func=Index3.as_view('index'))
c = app.test_client()
rv = c.open('/', method='OPTIONS')
assert 'OPTIONS' in rv.allow
def test_implicit_head(app, client):
class Index(flask.views.MethodView): class Index(flask.views.MethodView):
def get(self): def get(self):
return flask.Response('Blub', headers={ return flask.Response('Blub', headers={
@ -119,37 +159,36 @@ def test_implicit_head():
}) })
app.add_url_rule('/', view_func=Index.as_view('index')) app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client() rv = client.get('/')
rv = c.get('/')
assert rv.data == b'Blub' assert rv.data == b'Blub'
assert rv.headers['X-Method'] == 'GET' assert rv.headers['X-Method'] == 'GET'
rv = c.head('/') rv = client.head('/')
assert rv.data == b'' assert rv.data == b''
assert rv.headers['X-Method'] == 'HEAD' assert rv.headers['X-Method'] == 'HEAD'
def test_explicit_head():
app = flask.Flask(__name__)
def test_explicit_head(app, client):
class Index(flask.views.MethodView): class Index(flask.views.MethodView):
def get(self): def get(self):
return 'GET' return 'GET'
def head(self): def head(self):
return flask.Response('', headers={'X-Method': 'HEAD'}) return flask.Response('', headers={'X-Method': 'HEAD'})
app.add_url_rule('/', view_func=Index.as_view('index')) app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client() rv = client.get('/')
rv = c.get('/')
assert rv.data == b'GET' assert rv.data == b'GET'
rv = c.head('/') rv = client.head('/')
assert rv.data == b'' assert rv.data == b''
assert rv.headers['X-Method'] == 'HEAD' assert rv.headers['X-Method'] == 'HEAD'
def test_endpoint_override():
app = flask.Flask(__name__) def test_endpoint_override(app):
app.debug = True app.debug = True
class Index(flask.views.View): class Index(flask.views.View):
methods = ['GET', 'POST'] methods = ['GET', 'POST']
def dispatch_request(self): def dispatch_request(self):
return flask.request.method return flask.request.method
@ -160,3 +199,41 @@ def test_endpoint_override():
# But these tests should still pass. We just log a warning. # But these tests should still pass. We just log a warning.
common_test(app) common_test(app)
def test_multiple_inheritance(app, client):
class GetView(flask.views.MethodView):
def get(self):
return 'GET'
class DeleteView(flask.views.MethodView):
def delete(self):
return 'DELETE'
class GetDeleteView(GetView, DeleteView):
pass
app.add_url_rule('/', view_func=GetDeleteView.as_view('index'))
assert client.get('/').data == b'GET'
assert client.delete('/').data == b'DELETE'
assert sorted(GetDeleteView.methods) == ['DELETE', 'GET']
def test_remove_method_from_parent(app, client):
class GetView(flask.views.MethodView):
def get(self):
return 'GET'
class OtherView(flask.views.MethodView):
def post(self):
return 'POST'
class View(GetView, OtherView):
methods = ['GET']
app.add_url_rule('/', view_func=View.as_view('index'))
assert client.get('/').data == b'GET'
assert client.post('/').status_code == 405
assert sorted(View.methods) == ['GET']

74
tox.ini

@ -1,33 +1,65 @@
[tox] [tox]
envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35,py36}-{release,devel}{,-simplejson} envlist =
py{36,35,34,33,27,26,py}-release
py{36,27,py}-release-simplejson
py{36,33,27,26,py}-devel
py{36,33,27,26,py}-lowest
docs-html
coverage-report
[testenv] [testenv]
passenv = LANG passenv = LANG
usedevelop=true usedevelop = true
commands = deps =
# We need to install those after Flask is installed. pytest>=3
pip install -e examples/flaskr coverage
pip install -e examples/minitwit
pip install -e examples/patterns/largerapp
pytest --cov=flask --cov-report html []
deps=
pytest
pytest-cov
greenlet greenlet
blinker
lowest: Werkzeug==0.7 lowest: Werkzeug==0.9
lowest: Jinja2==2.4 lowest: Jinja2==2.4
lowest: itsdangerous==0.21 lowest: itsdangerous==0.21
lowest: blinker==1.0 lowest: Click==4.0
release: blinker
devel: git+https://github.com/pallets/werkzeug.git devel: https://github.com/pallets/werkzeug/archive/master.tar.gz
devel: git+https://github.com/pallets/jinja.git devel: https://github.com/pallets/markupsafe/archive/master.tar.gz
devel: git+https://github.com/pallets/itsdangerous.git devel: https://github.com/pallets/jinja/archive/master.tar.gz
devel: git+https://github.com/jek/blinker.git devel: https://github.com/pallets/itsdangerous/archive/master.tar.gz
devel: https://github.com/pallets/click/archive/master.tar.gz
simplejson: simplejson simplejson: simplejson
commands =
# the examples need to be installed to test successfully
pip install -e examples/flaskr -q
pip install -e examples/minitwit -q
pip install -e examples/patterns/largerapp -q
[testenv:docs] # pytest-cov doesn't seem to play nice with -p
coverage run -p -m pytest
[testenv:docs-html]
deps =
sphinx
flask-sphinx-themes
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
[testenv:docs-linkcheck]
deps = sphinx deps = sphinx
commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/linkcheck commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/linkcheck
[testenv:coverage-report]
deps = coverage
skip_install = true
commands =
coverage combine
coverage report
coverage html
[testenv:codecov]
passenv = CI TRAVIS TRAVIS_*
deps = codecov
skip_install = true
commands =
coverage combine
coverage report
codecov

Loading…
Cancel
Save