Browse Source

Merge branch 'master' into overridable-cli

pull/2053/head
Kenneth Reitz 8 years ago committed by GitHub
parent
commit
120941ec72
  1. 11
      .coveragerc
  2. 3
      .gitmodules
  3. 89
      .travis.yml
  4. 17
      CHANGES
  5. 51
      CONTRIBUTING.rst
  6. 4
      Makefile
  7. BIN
      docs/_static/pycharm-runconfig.png
  8. 2
      docs/_templates/sidebarintro.html
  9. 1
      docs/_themes
  10. 54
      docs/appcontext.rst
  11. 43
      docs/cli.rst
  12. 21
      docs/conf.py
  13. 2
      docs/deploying/index.rst
  14. 16
      docs/deploying/wsgi-standalone.rst
  15. 32
      docs/extensiondev.rst
  16. 86
      docs/flaskext.py
  17. 80
      docs/patterns/celery.rst
  18. 14
      docs/quickstart.rst
  19. 15
      docs/reqcontext.rst
  20. 91
      docs/security.rst
  21. 4
      docs/signals.rst
  22. 204
      docs/testing.rst
  23. 3
      docs/tutorial/dbcon.rst
  24. 31
      docs/tutorial/dbinit.rst
  25. 14
      docs/tutorial/folders.rst
  26. 18
      docs/tutorial/index.rst
  27. 2
      docs/tutorial/introduction.rst
  28. 50
      docs/tutorial/packaging.rst
  29. 66
      docs/tutorial/setup.rst
  30. 3
      docs/tutorial/templates.rst
  31. 3
      docs/tutorial/views.rst
  32. 12
      examples/flaskr/tests/test_flaskr.py
  33. 12
      examples/minitwit/tests/test_minitwit.py
  34. 2
      flask/_compat.py
  35. 26
      flask/app.py
  36. 67
      flask/cli.py
  37. 36
      flask/helpers.py
  38. 98
      flask/sessions.py
  39. 4
      flask/views.py
  40. 3
      setup.cfg
  41. 4
      setup.py
  42. 2
      test-requirements.txt
  43. 71
      tests/conftest.py
  44. 96
      tests/test_appctx.py
  45. 838
      tests/test_basic.py
  46. 455
      tests/test_blueprints.py
  47. 100
      tests/test_cli.py
  48. 15
      tests/test_deprecations.py
  49. 22
      tests/test_ext.py
  50. 692
      tests/test_helpers.py
  51. 11
      tests/test_regression.py
  52. 121
      tests/test_reqctx.py
  53. 12
      tests/test_signals.py
  54. 214
      tests/test_templating.py
  55. 176
      tests/test_testing.py
  56. 79
      tests/test_user_error_handler.py
  57. 105
      tests/test_views.py
  58. 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

3
.gitmodules vendored

@ -1,3 +0,0 @@
[submodule "docs/_themes"]
path = docs/_themes
url = https://github.com/mitsuhiko/flask-sphinx-themes.git

89
.travis.yml

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

17
CHANGES

@ -42,6 +42,12 @@ Major release, unreleased
(`#2282`_)
- Allow overriding built-in commands using ``app.cli.command`` instead of
creating a separate ``FlaskGroup``. (`#2217`_)
- 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
@ -53,6 +59,15 @@ Major release, unreleased
.. _#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
--------------
@ -172,6 +187,8 @@ Released on May 29th 2016, codename Absinthe.
- Don't leak exception info of already catched exceptions to context teardown
handlers (pull request ``#1393``).
- Allow custom Jinja environment subclasses (pull request ``#1422``).
- Updated extension dev guidelines.
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
- Turn on autoescape for ``flask.templating.render_template_string`` by default
(pull request ``#1515``).

51
CONTRIBUTING.rst

@ -31,6 +31,42 @@ Submitting patches
- 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.
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
---------------------
@ -52,10 +88,20 @@ Install Flask as an editable package using the current source::
cd flask
pip install --editable .
Running the testsuite
---------------------
The minimal requirement for running the testsuite is ``pytest``. You can
install it with::
pip install pytest
Then you can run the testsuite with::
pytest tests/
**Shortcut**: ``make test`` will ensure ``pytest`` is installed, and run it.
With only pytest installed, a large part of the testsuite will get skipped
though. Whether this is relevant depends on which part of Flask you're working
on. Travis is set up to run the full testsuite when you submit your pull
@ -69,6 +115,8 @@ of ``pytest``. You can install it with::
The ``tox`` command will then run all tests against multiple combinations
Python versions and dependency versions.
**Shortcut**: ``make tox-test`` will ensure ``tox`` is installed, and run it.
Running test coverage
---------------------
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
**Shortcut**: ``make cov`` will ensure ``pytest-cov`` is installed, run it, display the results, *and* save the HTML report.
Caution
=======
pushing

4
Makefile

@ -6,6 +6,10 @@ test:
pip install -r test-requirements.txt
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:
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>
<p>
Flask is a micro webdevelopment framework for Python. You are currently
Flask is a micro web development framework for Python. You are currently
looking at the documentation of the development version.
</p>
<h3>Other Formats</h3>

1
docs/_themes

@ -1 +0,0 @@
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9

54
docs/appcontext.rst

@ -5,31 +5,37 @@ The Application Context
.. versionadded:: 0.9
One of the design ideas behind Flask is that there are two different
“states” in which code is executed. The application setup state in which
the application implicitly is on the module level. It starts when the
:class:`Flask` object is instantiated, and it implicitly ends when the
first request comes in. While the application is in this state a few
assumptions are true:
- the programmer can modify the application object safely.
- no request handling happened so far
- you have to have a reference to the application object in order to
modify it, there is no magic proxy that can give you a reference to
the application object you're currently creating or modifying.
In contrast, during request handling, a couple of other rules exist:
- while a request is active, the context local objects
One of the design ideas behind Flask is that there are at least two
different “states” in which code is executed:
1. The application setup state, in which the application implicitly is
on the module level.
This state starts when the :class:`Flask` object is instantiated, and
it implicitly ends when the first request comes in. While the
application is in this state, a few assumptions are true:
- the programmer can modify the application object safely.
- no request handling happened so far
- you have to have a reference to the application object in order to
modify it, there is no magic proxy that can give you a reference to
the application object you're currently creating or modifying.
2. In contrast, in the request handling state, a couple of other rules
exist:
- while a request is active, the context local objects
(:data:`flask.request` and others) point to the current request.
- any code can get hold of these objects at any time.
There is a third state which is sitting in between a little bit.
Sometimes you are dealing with an application in a way that is similar to
how you interact with applications during request handling; just that there
is no request active. Consider, for instance, that you're sitting in an
interactive Python shell and interacting with the application, or a
command line application.
- any code can get hold of these objects at any time.
3. There is also a third state somewhere in between 'module-level' and
'request-handling':
Sometimes you are dealing with an application in a way that is similar to
how you interact with applications during request handling, but without
there being an active request. Consider, for instance, that you're
sitting in an interactive Python shell and interacting with the
application, or a command line application.
The application context is what powers the :data:`~flask.current_app`
context local.

43
docs/cli.rst

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

21
docs/conf.py

@ -22,7 +22,6 @@ BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.join(os.path.dirname(__file__), '_themes'))
sys.path.append(os.path.dirname(__file__))
# -- General configuration -----------------------------------------------------
@ -121,7 +120,7 @@ exclude_patterns = ['_build']
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
# html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@ -273,21 +272,9 @@ intersphinx_mapping = {
'blinker': ('https://pythonhosted.org/blinker/', None)
}
try:
__import__('flask_theme_support')
pygments_style = 'flask_theme_support.FlaskyStyle'
html_theme = 'flask'
html_theme_options = {
'touch_icon': 'touch-icon.png'
}
except ImportError:
print('-' * 74)
print('Warning: Flask themes unavailable. Building with default theme')
print('If you want the Flask themes, run this command and build again:')
print()
print(' git submodule update --init')
print('-' * 74)
html_theme_options = {
'touch_icon': 'touch-icon.png'
}
# unwrap decorators
def unwrap_decorators():

2
docs/deploying/index.rst

@ -32,8 +32,8 @@ Self-hosted options
.. toctree::
:maxdepth: 2
mod_wsgi
wsgi-standalone
uwsgi
mod_wsgi
fastcgi
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/
.. _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
-------

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

86
docs/flaskext.py

@ -1,86 +0,0 @@
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}

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

14
docs/quickstart.rst

@ -50,7 +50,14 @@ to tell your terminal the application to work with by exporting the
$ flask run
* Running on http://127.0.0.1:5000/
If you are on Windows you need to use ``set`` instead of ``export``.
If you are on Windows, the environment variable syntax depends on command line
interpreter. On Command Prompt::
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`::
@ -153,6 +160,11 @@ Screenshot of the debugger in action:
:class: screenshot
:alt: screenshot of debugger in action
More information on using the debugger can be found in the `Werkzeug
documentation`_.
.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/debug/#using-the-debugger
Have another debugger in mind? See :ref:`working-with-debuggers`.

15
docs/reqcontext.rst

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

91
docs/security.rst

@ -104,3 +104,94 @@ vulnerabilities
<https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so
this behavior was changed and :func:`~flask.jsonify` now supports serializing
arrays.
Security Headers
----------------
This section contains a list of HTTP security headers supported by Flask.
To configure HTTPS and handle the headers listed below we suggest the package `flask-talisman <https://github.com/GoogleCloudPlatform/flask-talisman>`_.
HTTP Strict Transport Security (HSTS)
-------------------------------------
Redirects HTTP requests to HTTPS on all URLs, preventing man-in-the-middle (MITM) attacks.
Example:
.. sourcecode:: none
Strict-Transport-Security: max-age=<expire-time
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
Strict-Transport-Security: max-age=<expire-time>; preload
See also `Strict Transport Security <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security>`_.
HTTP Public Key Pinning (HPKP)
------------------------------
This enables your web server to authenticate with a client browser using a specific certificate key to prevent man-in-the-middle (MITM) attacks.
Example:
.. sourcecode:: none
Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubDomains][; report-uri="reportURI"]
See also `Public Key Pinning <https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning>`_.
X-Frame-Options (Clickjacking Protection)
-----------------------------------------
Prevents the client from clicking page elements outside of the website, avoiding hijacking or UI redress attacks.
.. sourcecode:: none
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://example.com/
See also `X-Frame-Options <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options>`_.
X-Content-Type-Options
----------------------
This header prevents Cross-site scripting (XSS) by blocking requests on clients and forcing them to first read and validate the content-type before reading any of the contents of the request.
.. sourcecode:: none
X-Content-Type-Options: nosniff
See also `X-Content-Type-Options <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options>`_.
Content Security Policy (CSP)
-----------------------------
Enhances security and prevents common web vulnerabilities such as cross-site scripting (XSS) and man-in-the-middle (MITM) related attacks.
Example:
.. sourcecode:: none
Content-Security-Policy: default-src https:; script-src 'nonce-{random}'; object-src 'none'
See also `Content Security Policy <https://csp.withgoogle.com/docs/index.html>`_.
Cookie Options
--------------
While these headers are not directly security related, they have important options that may affect your Flask application.
- ``Secure`` limits your cookies to HTTPS traffic only.
- ``HttpOnly`` protects the contents of your cookie from being visible to XSS.
- ``SameSite`` ensures that cookies can only be requested from the same domain that created them but this feature is not yet fully supported across all browsers.
Example:
.. sourcecode:: none
Set-Cookie: [cookie-name]=[cookie-value]
See also:
- Mozilla guide to `HTTP cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies>`_.
- `OWASP HTTP Only <https://www.owasp.org/index.php/HttpOnly>`_.

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

204
docs/testing.rst

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

3
docs/tutorial/dbcon.rst

@ -3,6 +3,9 @@
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
`connect_db`, but by itself, it is not particularly useful. Creating and
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
that schema.
Such a schema can be created by piping the ``schema.sql`` file into the
`sqlite3` command as follows::
Such a schema could be created by piping the ``schema.sql`` file into the
``sqlite3`` command as follows::
sqlite3 /tmp/flaskr.db < schema.sql
The downside of this is that it requires the ``sqlite3`` command to be
installed, which is not necessarily the case on every system. This also
requires that you provide the path to the database, which can introduce
errors. It's a good idea to add a function that initializes the database
for you, to the application.
However, the downside of this is that it requires the ``sqlite3`` command
to be installed, which is not necessarily the case on every system. This
also requires that you provide the path to the database, which can introduce
errors.
To do this, you can create a function and hook it into a :command:`flask`
command that initializes the database. For now just 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`::
Instead of the ``sqlite3`` command above, it's a good idea to add a function
to our application that initializes the database for you. To do this, you
can create a function and hook it into a :command:`flask` command that
initializes the database.
Take a look at the code segment below. A good place to add this function,
and command, is just below the ``connect_db`` function in :file:`flaskr.py`::
def init_db():
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
@app.cli.command('initdb')
def initdb_command():
"""Initializes the database."""
init_db()
print('Initialized the database.')
@ -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
databases will not commit unless you explicitly tell it to.
Now, it is possible to create a database with the :command:`flask` script::
Now, in a terminal, from the application root directory :file:`flaskr/` it is
possible to create a database with the :command:`flask` script::
flask initdb
Initialized the database.

14
docs/tutorial/folders.rst

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

18
docs/tutorial/index.rst

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

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
one of the popular NoSQL databases if your data is more suited for those.
Here a screenshot of the final application:
Here is a screenshot of the final application:
.. image:: ../_static/flaskr.png
:align: center

50
docs/tutorial/packaging.rst

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

66
docs/tutorial/setup.rst

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

3
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
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

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
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
------------

12
examples/flaskr/tests/test_flaskr.py

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

12
examples/minitwit/tests/test_minitwit.py

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

2
flask/_compat.py

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

26
flask/app.py

@ -133,8 +133,6 @@ class Flask(_PackageBoundObject):
:param static_folder: the folder with static files that should be served
at `static_url_path`. Defaults to the ``'static'``
folder in the root path of the application.
folder in the root path of the application. Defaults
to None.
: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
@ -222,7 +220,7 @@ class Flask(_PackageBoundObject):
#: The testing flag. Set this to ``True`` to enable the test mode of
#: 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.
#:
#: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
@ -1462,15 +1460,17 @@ class Flask(_PackageBoundObject):
return f
def _find_error_handler(self, e):
"""Finds a registered error handler for the request’s blueprint.
Otherwise falls back to the app, returns None if not a suitable
handler is found.
"""Find a registered error handler for a request in this order:
blueprint handler for a specific code, app handler for a specific code,
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))
def find_handler(handler_map):
if not handler_map:
return
for cls in exc_class.__mro__:
handler = handler_map.get(cls)
if handler is not None:
@ -1478,15 +1478,13 @@ class Flask(_PackageBoundObject):
handler_map[exc_class] = handler
return handler
# try blueprint handlers
handler = find_handler(self.error_handler_spec
.get(request.blueprint, {})
.get(code))
if handler is not None:
return handler
# check for any in blueprint or app
for name, c in ((request.blueprint, code), (None, code),
(request.blueprint, None), (None, None)):
handler = find_handler(self.error_handler_spec.get(name, {}).get(c))
# fall back to app handlers
return find_handler(self.error_handler_spec[None].get(code))
if handler:
return handler
def handle_http_exception(self, e):
"""Handles an HTTP exception. By default this will invoke the

67
flask/cli.py

@ -22,34 +22,75 @@ from . import __version__
from ._compat import iteritems, reraise
from .globals import current_app
from .helpers import get_debug_flag
from ._compat import getargspec
class NoAppException(click.UsageError):
"""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
application in the module or raises an exception.
"""
from . import Flask
# 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)
if app is not None and isinstance(app, Flask):
if isinstance(app, Flask):
return app
# Otherwise find the only object that is a Flask instance.
matches = [v for k, v in iteritems(module.__dict__)
if isinstance(v, Flask)]
matches = [
v for k, v in iteritems(module.__dict__) if isinstance(v, Flask)
]
if len(matches) == 1:
return matches[0]
raise NoAppException('Failed to find application in module "%s". Are '
'you sure it contains a Flask application? Maybe '
'you wrapped it in a WSGI middleware or you are '
'using a factory function.' % module.__name__)
elif len(matches) > 1:
raise NoAppException(
'Auto-detected multiple Flask applications in module "{module}".'
' 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):
@ -81,7 +122,7 @@ def prepare_exec_for_file(filename):
return '.'.join(module[::-1])
def locate_app(app_id):
def locate_app(script_info, app_id):
"""Attempts to locate the application."""
__traceback_hide__ = True
if ':' in app_id:
@ -107,7 +148,7 @@ def locate_app(app_id):
mod = sys.modules[module]
if app_obj is None:
app = find_best_app(mod)
app = find_best_app(script_info, mod)
else:
app = getattr(mod, app_obj, None)
if app is None:
@ -232,7 +273,7 @@ class ScriptInfo(object):
if self._loaded_app is not None:
return self._loaded_app
if self.create_app is not None:
rv = self.create_app(self)
rv = call_factory(self.create_app, self)
else:
if not self.app_import_path:
raise NoAppException(
@ -240,7 +281,7 @@ class ScriptInfo(object):
'the FLASK_APP environment variable.\n\nFor more '
'information see '
'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()
if debug is not None:
rv.debug = debug

36
flask/helpers.py

@ -638,18 +638,24 @@ def safe_join(directory, *pathnames):
:raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
paths fall out of its boundaries.
"""
parts = [directory]
for filename in pathnames:
if filename != '':
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
if (
any(sep in filename for sep in _os_alt_seps)
or os.path.isabs(filename)
or filename == '..'
or filename.startswith('../')
):
raise NotFound()
directory = os.path.join(directory, filename)
return directory
parts.append(filename)
return posixpath.join(*parts)
def send_from_directory(directory, filename, **options):
@ -998,3 +1004,17 @@ def is_ip(value):
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

98
flask/sessions.py

@ -9,18 +9,20 @@
:license: BSD, see LICENSE for more details.
"""
import uuid
import hashlib
import uuid
import warnings
from base64 import b64encode, b64decode
from base64 import b64decode, b64encode
from datetime import datetime
from werkzeug.http import http_date, parse_date
from itsdangerous import BadSignature, URLSafeTimedSerializer
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 ._compat import iteritems, text_type
from .helpers import total_seconds, is_ip
from itsdangerous import URLSafeTimedSerializer, BadSignature
from .helpers import is_ip, total_seconds
class SessionMixin(object):
@ -49,6 +51,13 @@ class SessionMixin(object):
#: The default mixin implementation just hardcodes ``True`` in.
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
def _tag(value):
if isinstance(value, tuple):
@ -117,8 +126,23 @@ class SecureCookieSession(CallbackDict, SessionMixin):
def __init__(self, initial=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.accessed = True
super(SecureCookieSession, self).__init__(initial, on_update)
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):
@ -290,22 +314,20 @@ class SessionInterface(object):
return datetime.utcnow() + app.permanent_session_lifetime
def should_set_cookie(self, app, session):
"""Indicates whether a cookie should be set now or not. This is
used by session backends to figure out if they should emit a
set-cookie header or not. The default behavior is controlled by
the ``SESSION_REFRESH_EACH_REQUEST`` config variable. If
it's set to ``False`` then a cookie is only set if the session is
modified, if set to ``True`` it's always set if the session is
permanent.
This check is usually skipped if sessions get deleted.
"""Used by session backends to determine if a ``Set-Cookie`` header
should be set for this session cookie for this response. If the session
has been modified, the cookie is set. If the session is permanent and
the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
always set.
This check is usually skipped if the session was deleted.
.. versionadded:: 0.11
"""
if session.modified:
return True
save_each = app.config['SESSION_REFRESH_EACH_REQUEST']
return save_each and session.permanent
return session.modified or (
session.permanent and app.config['SESSION_REFRESH_EACH_REQUEST']
)
def open_session(self, app, request):
"""This method has to be implemented and must either return ``None``
@ -371,22 +393,22 @@ class SecureCookieSessionInterface(SessionInterface):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
# Delete case. If there is no session we bail early.
# If the session was modified to be empty we remove the
# whole cookie.
# If the session is modified to be empty, remove the cookie.
# If the session is empty, return without setting the cookie.
if not session:
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
response.delete_cookie(
app.session_cookie_name,
domain=domain,
path=path
)
return
# Modification case. There are upsides and downsides to
# emitting a set-cookie header each request. The behavior
# is controlled by the :meth:`should_set_cookie` method
# 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.
# Add a "Vary: Cookie" header if the session was accessed at all.
if session.accessed:
patch_vary_header(response, 'Cookie')
if not self.should_set_cookie(app, session):
return
@ -394,6 +416,12 @@ class SecureCookieSessionInterface(SessionInterface):
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie(app.session_cookie_name, val,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
response.set_cookie(
app.session_cookie_name,
val,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure
)

4
flask/views.py

@ -51,6 +51,9 @@ class View(object):
#: A list of methods this view can handle.
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
#: return value of as_view(). However since this moves parts of the
#: logic from the class declaration to the place where it's hooked
@ -99,6 +102,7 @@ class View(object):
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options
return view

3
setup.cfg

@ -8,4 +8,5 @@ universal = 1
license_file = LICENSE
[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,
platforms='any',
install_requires=[
'Werkzeug>=0.7',
'Werkzeug>=0.9',
'Jinja2>=2.4',
'itsdangerous>=0.21',
'click>=2.0',
'click>=4.0',
],
classifiers=[
'Development Status :: 4 - Beta',

2
test-requirements.txt

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

71
tests/conftest.py

@ -13,6 +13,40 @@ import sys
import pkgutil
import pytest
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
@ -22,16 +56,17 @@ def test_apps(monkeypatch):
os.path.dirname(__file__), 'test_apps'))
)
@pytest.fixture(autouse=True)
def leak_detector(request):
def ensure_clean_request_context():
# make sure we're not leaking a request context since we are
# testing flask internally in debug mode in a few cases
leaks = []
while flask._request_ctx_stack.top is not None:
leaks.append(flask._request_ctx_stack.pop())
assert leaks == []
request.addfinalizer(ensure_clean_request_context)
def leak_detector():
yield
# make sure we're not leaking a request context since we are
# testing flask internally in debug mode in a few cases
leaks = []
while flask._request_ctx_stack.top is not None:
leaks.append(flask._request_ctx_stack.pop())
assert leaks == []
@pytest.fixture(params=(True, False))
@ -62,12 +97,13 @@ def limit_loader(request, monkeypatch):
def get_loader(*args, **kwargs):
return LimitedLoader(old_get_loader(*args, **kwargs))
monkeypatch.setattr(pkgutil, 'get_loader', get_loader)
@pytest.fixture
def modules_tmpdir(tmpdir, monkeypatch):
'''A tmpdir added to sys.path'''
"""A tmpdir added to sys.path."""
rv = tmpdir.mkdir('modules_tmpdir')
monkeypatch.syspath_prepend(str(rv))
return rv
@ -81,10 +117,10 @@ def modules_tmpdir_prefix(modules_tmpdir, monkeypatch):
@pytest.fixture
def site_packages(modules_tmpdir, monkeypatch):
'''Create a fake site-packages'''
"""Create a fake site-packages."""
rv = modules_tmpdir \
.mkdir('lib')\
.mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info))\
.mkdir('lib') \
.mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info)) \
.mkdir('site-packages')
monkeypatch.syspath_prepend(str(rv))
return rv
@ -92,8 +128,9 @@ def site_packages(modules_tmpdir, monkeypatch):
@pytest.fixture
def install_egg(modules_tmpdir, monkeypatch):
'''Generate egg from package name inside base and put the egg into
sys.path'''
"""Generate egg from package name inside base and put the egg into
sys.path."""
def inner(name, base=modules_tmpdir):
if not isinstance(name, str):
raise ValueError(name)
@ -117,6 +154,7 @@ def install_egg(modules_tmpdir, monkeypatch):
egg_path, = modules_tmpdir.join('dist/').listdir()
monkeypatch.syspath_prepend(str(egg_path))
return egg_path
return inner
@ -124,6 +162,7 @@ def install_egg(modules_tmpdir, monkeypatch):
def purge_module(request):
def inner(name):
request.addfinalizer(lambda: sys.modules.pop(name, None))
return inner
@ -131,4 +170,4 @@ def purge_module(request):
def catch_deprecation_warnings(recwarn):
yield
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
def test_basic_url_generation():
app = flask.Flask(__name__)
def test_basic_url_generation(app):
app.config['SERVER_NAME'] = 'localhost'
app.config['PREFERRED_URL_SCHEME'] = 'https'
@ -27,31 +26,33 @@ def test_basic_url_generation():
rv = flask.url_for('index')
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 pytest.raises(RuntimeError):
flask.url_for('index')
def test_url_generation_without_context_fails():
with pytest.raises(RuntimeError):
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():
assert flask.current_app._get_current_object() == app
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():
assert flask.current_app._get_current_object() == app
assert flask._app_ctx_stack.top is None
def test_app_tearing_down():
def test_app_tearing_down(app):
cleanup_stuff = []
app = flask.Flask(__name__)
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
@ -61,9 +62,10 @@ def test_app_tearing_down():
assert cleanup_stuff == [None]
def test_app_tearing_down_with_previous_exception():
def test_app_tearing_down_with_previous_exception(app):
cleanup_stuff = []
app = flask.Flask(__name__)
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
@ -78,9 +80,10 @@ def test_app_tearing_down_with_previous_exception():
assert cleanup_stuff == [None]
def test_app_tearing_down_with_handled_exception():
def test_app_tearing_down_with_handled_exception(app):
cleanup_stuff = []
app = flask.Flask(__name__)
@app.teardown_appcontext
def cleanup(exception):
cleanup_stuff.append(exception)
@ -93,46 +96,49 @@ def test_app_tearing_down_with_handled_exception():
assert cleanup_stuff == [None]
def test_app_ctx_globals_methods():
app = flask.Flask(__name__)
with app.app_context():
# get
assert flask.g.get('foo') is None
assert flask.g.get('foo', 'bar') == 'bar'
# __contains__
assert 'foo' not in flask.g
flask.g.foo = 'bar'
assert 'foo' in flask.g
# setdefault
flask.g.setdefault('bar', 'the cake is a lie')
flask.g.setdefault('bar', 'hello world')
assert flask.g.bar == 'the cake is a lie'
# pop
assert flask.g.pop('bar') == 'the cake is a lie'
with pytest.raises(KeyError):
flask.g.pop('bar')
assert flask.g.pop('bar', 'more cake') == 'more cake'
# __iter__
assert list(flask.g) == ['foo']
def test_custom_app_ctx_globals_class():
def test_app_ctx_globals_methods(app, app_ctx):
# get
assert flask.g.get('foo') is None
assert flask.g.get('foo', 'bar') == 'bar'
# __contains__
assert 'foo' not in flask.g
flask.g.foo = 'bar'
assert 'foo' in flask.g
# setdefault
flask.g.setdefault('bar', 'the cake is a lie')
flask.g.setdefault('bar', 'hello world')
assert flask.g.bar == 'the cake is a lie'
# pop
assert flask.g.pop('bar') == 'the cake is a lie'
with pytest.raises(KeyError):
flask.g.pop('bar')
assert flask.g.pop('bar', 'more cake') == 'more cake'
# __iter__
assert list(flask.g) == ['foo']
def test_custom_app_ctx_globals_class(app):
class CustomRequestGlobals(object):
def __init__(self):
self.spam = 'eggs'
app = flask.Flask(__name__)
app.app_ctx_globals_class = CustomRequestGlobals
with app.app_context():
assert flask.render_template_string('{{ g.spam }}') == 'eggs'
def test_context_refcounts():
def test_context_refcounts(app, client):
called = []
app = flask.Flask(__name__)
@app.teardown_request
def teardown_req(error=None):
called.append('request')
@app.teardown_appcontext
def teardown_app(error=None):
called.append('app')
@app.route('/')
def index():
with flask._app_ctx_stack.top:
@ -141,16 +147,16 @@ def test_context_refcounts():
env = flask._request_ctx_stack.top.request.environ
assert env['werkzeug.request'] is not None
return u''
c = app.test_client()
res = c.get('/')
res = client.get('/')
assert res.status_code == 200
assert res.data == b''
assert called == ['request', 'app']
def test_clean_pop():
def test_clean_pop(app):
app.testing = False
called = []
app = flask.Flask(__name__)
@app.teardown_request
def teardown_req(error=None):
@ -166,5 +172,5 @@ def test_clean_pop():
except ZeroDivisionError:
pass
assert called == ['test_appctx', 'TEARDOWN']
assert called == ['conftest', 'TEARDOWN']
assert not flask.current_app

838
tests/test_basic.py

File diff suppressed because it is too large Load Diff

455
tests/test_blueprints.py

@ -18,7 +18,7 @@ from werkzeug.http import parse_cache_control_header
from jinja2 import TemplateNotFound
def test_blueprint_specific_error_handling():
def test_blueprint_specific_error_handling(app, client):
frontend = flask.Blueprint('frontend', __name__)
backend = flask.Blueprint('backend', __name__)
sideend = flask.Blueprint('sideend', __name__)
@ -43,7 +43,6 @@ def test_blueprint_specific_error_handling():
def sideend_no():
flask.abort(403)
app = flask.Flask(__name__)
app.register_blueprint(frontend)
app.register_blueprint(backend)
app.register_blueprint(sideend)
@ -52,15 +51,15 @@ def test_blueprint_specific_error_handling():
def app_forbidden(e):
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):
pass
class MyFunctionException(Exception):
pass
@ -74,24 +73,48 @@ def test_blueprint_specific_user_error_handling():
def my_function_exception_handler(e):
assert isinstance(e, MyFunctionException)
return 'bam'
blue.register_error_handler(MyFunctionException, my_function_exception_handler)
@blue.route('/decorator')
def blue_deco_test():
raise MyDecoratorException()
@blue.route('/function')
def blue_func_test():
raise MyFunctionException()
app = flask.Flask(__name__)
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__)
assert c.get('/decorator').data == b'boom'
assert c.get('/function').data == b'bam'
@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)
def test_blueprint_url_definitions():
app.register_blueprint(errors)
app.register_blueprint(forbidden_bp)
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.route('/foo', defaults={'baz': 42})
@ -102,17 +125,16 @@ def test_blueprint_url_definitions():
def bar(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='/2', url_defaults={'bar': 19})
c = app.test_client()
assert c.get('/1/foo').data == b'23/42'
assert c.get('/2/foo').data == b'19/42'
assert c.get('/1/bar').data == b'23'
assert c.get('/2/bar').data == b'19'
assert client.get('/1/foo').data == b'23/42'
assert client.get('/2/foo').data == b'19/42'
assert client.get('/1/bar').data == b'23'
assert client.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.url_defaults
@ -131,28 +153,26 @@ def test_blueprint_url_processors():
def about():
return flask.url_for('.index')
app = flask.Flask(__name__)
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):
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'
rv = c.get('/admin/')
rv = client.get('/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'
rv = c.get('/admin/static/test.txt')
rv = client.get('/admin/static/test.txt')
assert rv.data.strip() == b'Admin File'
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 */'
rv.close()
@ -163,7 +183,7 @@ def test_templates_and_static(test_apps):
if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age:
expected_max_age = 7200
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'])
assert cc.max_age == expected_max_age
rv.close()
@ -181,8 +201,8 @@ def test_templates_and_static(test_apps):
with flask.Flask(__name__).test_request_context():
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):
def get_send_file_max_age(self, filename):
return 100
@ -205,12 +225,14 @@ def test_default_static_cache_timeout():
finally:
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
def test_templates_list(test_apps):
from blueprintapp import app
templates = sorted(app.jinja_env.list_templates())
assert templates == ['admin/index.html', 'frontend/index.html']
def test_dotted_names():
def test_dotted_names(app, client):
frontend = flask.Blueprint('myapp.frontend', __name__)
backend = flask.Blueprint('myapp.backend', __name__)
@ -226,18 +248,15 @@ def test_dotted_names():
def backend_index():
return flask.url_for('myapp.frontend.frontend_index')
app = flask.Flask(__name__)
app.register_blueprint(frontend)
app.register_blueprint(backend)
c = app.test_client()
assert c.get('/fe').data.strip() == b'/be'
assert c.get('/fe2').data.strip() == b'/fe'
assert c.get('/be').data.strip() == b'/fe'
assert client.get('/fe').data.strip() == b'/be'
assert client.get('/fe2').data.strip() == b'/fe'
assert client.get('/be').data.strip() == b'/fe'
def test_dotted_names_from_app():
app = flask.Flask(__name__)
app.testing = True
def test_dotted_names_from_app(app, client):
test = flask.Blueprint('test', __name__)
@app.route('/')
@ -250,11 +269,11 @@ def test_dotted_names_from_app():
app.register_blueprint(test)
with app.test_client() as c:
rv = c.get('/')
assert rv.data == b'/test/'
rv = client.get('/')
assert rv.data == b'/test/'
def test_empty_url_defaults():
def test_empty_url_defaults(app, client):
bp = flask.Blueprint('bp', __name__)
@bp.route('/', defaults={'page': 1})
@ -262,15 +281,13 @@ def test_empty_url_defaults():
def something(page):
return str(page)
app = flask.Flask(__name__)
app.register_blueprint(bp)
c = app.test_client()
assert c.get('/').data == b'1'
assert c.get('/page/2').data == b'2'
assert client.get('/').data == b'1'
assert client.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.route('/foo')
@ -289,21 +306,20 @@ def test_route_decorator_custom_endpoint():
def bar_foo():
return flask.request.endpoint
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.request.endpoint
c = app.test_client()
assert c.get('/').data == b'index'
assert c.get('/py/foo').data == b'bp.foo'
assert c.get('/py/bar').data == b'bp.bar'
assert c.get('/py/bar/123').data == b'bp.123'
assert c.get('/py/bar/foo').data == b'bp.bar_foo'
assert client.get('/').data == b'index'
assert client.get('/py/foo').data == b'bp.foo'
assert client.get('/py/bar').data == b'bp.bar'
assert client.get('/py/bar/123').data == b'bp.123'
assert client.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.route('/foo')
@ -344,21 +360,18 @@ def test_route_decorator_custom_endpoint_with_dots():
lambda: None
)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
c = app.test_client()
assert c.get('/py/foo').data == b'bp.foo'
assert client.get('/py/foo').data == b'bp.foo'
# The rule's didn't actually made it through
rv = c.get('/py/bar')
rv = client.get('/py/bar')
assert rv.status_code == 404
rv = c.get('/py/bar/123')
rv = client.get('/py/bar/123')
assert rv.status_code == 404
def test_endpoint_decorator():
def test_endpoint_decorator(app, client):
from werkzeug.routing import Rule
app = flask.Flask(__name__)
app.url_map.add(Rule('/foo', endpoint='bar'))
bp = flask.Blueprint('bp', __name__)
@ -369,225 +382,439 @@ def test_endpoint_decorator():
app.register_blueprint(bp, url_prefix='/bp_prefix')
c = app.test_client()
assert c.get('/foo').data == b'bar'
assert c.get('/bp_prefix/bar').status_code == 404
assert client.get('/foo').data == b'bar'
assert client.get('/bp_prefix/bar').status_code == 404
def test_template_filter():
def test_template_filter(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter()
def my_reverse(s):
return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
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']('abcd') == 'dcba'
def test_add_template_filter():
def test_add_template_filter(app):
bp = flask.Blueprint('bp', __name__)
def my_reverse(s):
return s[::-1]
bp.add_app_template_filter(my_reverse)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
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']('abcd') == 'dcba'
def test_template_filter_with_name():
def test_template_filter_with_name(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter('strrev')
def my_reverse(s):
return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse
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__)
def my_reverse(s):
return s[::-1]
bp.add_app_template_filter(my_reverse, 'strrev')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
assert 'strrev' in app.jinja_env.filters.keys()
assert app.jinja_env.filters['strrev'] == my_reverse
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.app_template_filter()
def super_reverse(s):
return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
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('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
bp = flask.Blueprint('bp', __name__)
@bp.app_template_filter()
def super_reverse(s):
return s[::-1]
app.register_blueprint(bp, url_prefix='/py')
rv = app.test_client().get('/')
rv = client.get('/')
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__)
def super_reverse(s):
return s[::-1]
bp.add_app_template_filter(super_reverse)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
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.app_template_filter('super_reverse')
def my_reverse(s):
return s[::-1]
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
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__)
def my_reverse(s):
return s[::-1]
bp.add_app_template_filter(my_reverse, 'super_reverse')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_filter.html', value='abcd')
rv = app.test_client().get('/')
rv = client.get('/')
assert rv.data == b'dcba'
def test_template_test():
def test_template_test(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test()
def is_boolean(value):
return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
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'](False)
def test_add_template_test():
def test_add_template_test(app):
bp = flask.Blueprint('bp', __name__)
def is_boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(is_boolean)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
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'](False)
def test_template_test_with_name():
def test_template_test_with_name(app):
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test('boolean')
def is_boolean(value):
return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean
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__)
def is_boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(is_boolean, 'boolean')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
assert 'boolean' in app.jinja_env.tests.keys()
assert app.jinja_env.tests['boolean'] == is_boolean
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.app_template_test()
def boolean(value):
return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
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('/')
def index():
return flask.render_template('template_test.html', value=False)
bp = flask.Blueprint('bp', __name__)
@bp.app_template_test()
def boolean(value):
return isinstance(value, bool)
app.register_blueprint(bp, url_prefix='/py')
rv = app.test_client().get('/')
rv = client.get('/')
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__)
def boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(boolean)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
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.app_template_test('boolean')
def is_boolean(value):
return isinstance(value, bool)
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
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__)
def is_boolean(value):
return isinstance(value, bool)
bp.add_app_template_test(is_boolean, 'boolean')
app = flask.Flask(__name__)
app.register_blueprint(bp, url_prefix='/py')
@app.route('/')
def index():
return flask.render_template('template_test.html', value=False)
rv = app.test_client().get('/')
rv = client.get('/')
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/'

100
tests/test_cli.py

@ -39,26 +39,90 @@ def test_cli_name(test_apps):
def test_find_best_app(test_apps):
"""Test if `find_best_app` behaves as expected with different combinations of input."""
script_info = ScriptInfo()
class Module:
app = Flask('appname')
assert find_best_app(Module) == Module.app
assert find_best_app(script_info, Module) == Module.app
class Module:
application = Flask('appname')
assert find_best_app(Module) == Module.application
assert find_best_app(script_info, Module) == Module.application
class Module:
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:
pass
pytest.raises(NoAppException, find_best_app, Module)
pytest.raises(NoAppException, find_best_app, script_info, Module)
class Module:
myapp1 = Flask('appname1')
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):
@ -83,13 +147,18 @@ def test_prepare_exec_for_file(test_apps):
def test_locate_app(test_apps):
"""Test of locate_app."""
assert locate_app("cliapp.app").name == "testapp"
assert locate_app("cliapp.app:testapp").name == "testapp"
assert locate_app("cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app, "notanpp.py")
pytest.raises(NoAppException, locate_app, "cliapp/app")
pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp")
pytest.raises(NoAppException, locate_app, "cliapp.importerrorapp")
script_info = ScriptInfo()
assert locate_app(script_info, "cliapp.app").name == "testapp"
assert locate_app(script_info, "cliapp.app:testapp").name == "testapp"
assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app,
script_info, "notanpp.py")
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):
@ -109,10 +178,13 @@ def test_get_version(test_apps, capsys):
"""Test of get_version."""
from flask import __version__ as flask_ver
from sys import version as py_ver
class MockCtx(object):
resilient_parsing = False
color = None
def exit(self): return
ctx = MockCtx()
get_version(ctx, None, "test")
out, err = capsys.readouterr()
@ -137,6 +209,7 @@ def test_scriptinfo(test_apps):
def test_with_appcontext(runner):
"""Test of with_appcontext."""
@click.command()
@with_appcontext
def testcmd():
@ -151,6 +224,7 @@ def test_with_appcontext(runner):
def test_appgroup(runner):
"""Test of with_appcontext."""
@click.group(cls=AppGroup)
def cli():
pass
@ -180,6 +254,7 @@ def test_appgroup(runner):
def test_flaskgroup(runner):
"""Test FlaskGroup."""
def create_app(info):
return Flask("flaskgroup")
@ -198,6 +273,7 @@ def test_flaskgroup(runner):
def test_print_exceptions(runner):
"""Print the stacktrace if the CLI."""
def create_app(info):
raise Exception("oh no")
return Flask("flaskgroup")

15
tests/test_deprecations.py

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

22
tests/test_ext.py

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

692
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.
"""
import pytest
import os
import gc
import sys
import flask
import threading
import pytest
from werkzeug.exceptions import NotFound
import flask
_gc_lock = threading.Lock()
@ -77,11 +76,9 @@ def test_safe_join_toplevel_pardir():
safe_join('/foo', '..')
def test_aborting():
def test_aborting(app):
class Foo(Exception):
whatever = 42
app = flask.Flask(__name__)
app.testing = True
@app.errorhandler(Foo)
def handle_foo(e):

121
tests/test_reqctx.py

@ -20,9 +20,9 @@ except ImportError:
greenlet = None
def test_teardown_on_pop():
def test_teardown_on_pop(app):
buffer = []
app = flask.Flask(__name__)
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
@ -33,9 +33,10 @@ def test_teardown_on_pop():
ctx.pop()
assert buffer == [None]
def test_teardown_with_previous_exception():
def test_teardown_with_previous_exception(app):
buffer = []
app = flask.Flask(__name__)
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
@ -49,9 +50,10 @@ def test_teardown_with_previous_exception():
assert buffer == []
assert buffer == [None]
def test_teardown_with_handled_exception():
def test_teardown_with_handled_exception(app):
buffer = []
app = flask.Flask(__name__)
@app.teardown_request
def end_of_request(exception):
buffer.append(exception)
@ -64,8 +66,8 @@ def test_teardown_with_handled_exception():
pass
assert buffer == [None]
def test_proper_test_request_context():
app = flask.Flask(__name__)
def test_proper_test_request_context(app):
app.config.update(
SERVER_NAME='localhost.localdomain:5000'
)
@ -80,11 +82,11 @@ def test_proper_test_request_context():
with app.test_request_context('/'):
assert flask.url_for('index', _external=True) == \
'http://localhost.localdomain:5000/'
'http://localhost.localdomain:5000/'
with app.test_request_context('/'):
assert flask.url_for('sub', _external=True) == \
'http://foo.localhost.localdomain:5000/'
'http://foo.localhost.localdomain:5000/'
try:
with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}):
@ -104,11 +106,12 @@ def test_proper_test_request_context():
with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}):
pass
def test_context_binding():
app = flask.Flask(__name__)
def test_context_binding(app):
@app.route('/')
def index():
return 'Hello %s!' % flask.request.args['name']
@app.route('/meh')
def meh():
return flask.request.url
@ -119,8 +122,8 @@ def test_context_binding():
assert meh() == 'http://localhost/meh'
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.has_request_context()
ctx = app.test_request_context()
@ -131,8 +134,8 @@ def test_context_test():
finally:
ctx.pop()
def test_manual_context_binding():
app = flask.Flask(__name__)
def test_manual_context_binding(app):
@app.route('/')
def index():
return 'Hello %s!' % flask.request.args['name']
@ -144,56 +147,60 @@ def test_manual_context_binding():
with pytest.raises(RuntimeError):
index()
@pytest.mark.skipif(greenlet is None, reason='greenlet not installed')
def test_greenlet_context_copying():
app = flask.Flask(__name__)
greenlets = []
class TestGreenletContextCopying(object):
@app.route('/')
def index():
reqctx = flask._request_ctx_stack.top.copy()
def g():
assert not flask.request
assert not flask.current_app
with reqctx:
def test_greenlet_context_copying(self, app, client):
greenlets = []
@app.route('/')
def index():
reqctx = flask._request_ctx_stack.top.copy()
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.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 = app.test_client().get('/?foo=bar')
assert rv.data == b'Hello World!'
return 42
result = greenlets[0].run()
assert result == 42
greenlets.append(greenlet(g))
return 'Hello World!'
@pytest.mark.skipif(greenlet is None, reason='greenlet not installed')
def test_greenlet_context_copying_api():
app = flask.Flask(__name__)
greenlets = []
rv = client.get('/?foo=bar')
assert rv.data == b'Hello World!'
@app.route('/')
def index():
reqctx = flask._request_ctx_stack.top.copy()
@flask.copy_current_request_context
def g():
assert flask.request
assert flask.current_app == app
assert flask.request.path == '/'
assert flask.request.args['foo'] == 'bar'
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
result = greenlets[0].run()
assert result == 42
def test_session_error_pops_context():

12
tests/test_signals.py

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

214
tests/test_templating.py

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

176
tests/test_testing.py

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

79
tests/test_user_error_handler.py

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

105
tests/test_views.py

@ -16,6 +16,7 @@ import flask.views
from werkzeug.http import parse_set_header
def common_test(app):
c = app.test_client()
@ -25,23 +26,23 @@ def common_test(app):
meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
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):
methods = ['GET', 'POST']
def dispatch_request(self):
return flask.request.method
app.add_url_rule('/', view_func=Index.as_view('index'))
common_test(app)
def test_method_based_view():
app = flask.Flask(__name__)
def test_method_based_view(app):
class Index(flask.views.MethodView):
def get(self):
return 'GET'
def post(self):
return 'POST'
@ -49,18 +50,19 @@ def test_method_based_view():
common_test(app)
def test_view_patching():
app = flask.Flask(__name__)
def test_view_patching(app):
class Index(flask.views.MethodView):
def get(self):
1 // 0
def post(self):
1 // 0
class Other(Index):
def get(self):
return 'GET'
def post(self):
return 'POST'
@ -69,12 +71,12 @@ def test_view_patching():
app.add_url_rule('/', view_func=view)
common_test(app)
def test_view_inheritance():
app = flask.Flask(__name__)
def test_view_inheritance(app, client):
class Index(flask.views.MethodView):
def get(self):
return 'GET'
def post(self):
return 'POST'
@ -83,35 +85,73 @@ def test_view_inheritance():
return 'DELETE'
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']
def test_view_decorators():
app = flask.Flask(__name__)
def test_view_decorators(app, client):
def add_x_parachute(f):
def new_function(*args, **kwargs):
resp = flask.make_response(f(*args, **kwargs))
resp.headers['X-Parachute'] = 'awesome'
return resp
return new_function
class Index(flask.views.View):
decorators = [add_x_parachute]
def dispatch_request(self):
return 'Awesome'
app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client()
rv = c.get('/')
rv = client.get('/')
assert rv.headers['X-Parachute'] == '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__)
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):
def get(self):
return flask.Response('Blub', headers={
@ -119,37 +159,36 @@ def test_implicit_head():
})
app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client()
rv = c.get('/')
rv = client.get('/')
assert rv.data == b'Blub'
assert rv.headers['X-Method'] == 'GET'
rv = c.head('/')
rv = client.head('/')
assert rv.data == b''
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):
def get(self):
return 'GET'
def head(self):
return flask.Response('', headers={'X-Method': 'HEAD'})
app.add_url_rule('/', view_func=Index.as_view('index'))
c = app.test_client()
rv = c.get('/')
rv = client.get('/')
assert rv.data == b'GET'
rv = c.head('/')
rv = client.head('/')
assert rv.data == b''
assert rv.headers['X-Method'] == 'HEAD'
def test_endpoint_override():
app = flask.Flask(__name__)
def test_endpoint_override(app):
app.debug = True
class Index(flask.views.View):
methods = ['GET', 'POST']
def dispatch_request(self):
return flask.request.method
@ -161,9 +200,8 @@ def test_endpoint_override():
# But these tests should still pass. We just log a warning.
common_test(app)
def test_multiple_inheritance():
app = flask.Flask(__name__)
def test_multiple_inheritance(app, client):
class GetView(flask.views.MethodView):
def get(self):
return 'GET'
@ -177,14 +215,12 @@ def test_multiple_inheritance():
app.add_url_rule('/', view_func=GetDeleteView.as_view('index'))
c = app.test_client()
assert c.get('/').data == b'GET'
assert c.delete('/').data == b'DELETE'
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 = flask.Flask(__name__)
def test_remove_method_from_parent(app, client):
class GetView(flask.views.MethodView):
def get(self):
return 'GET'
@ -198,7 +234,6 @@ def test_remove_method_from_parent():
app.add_url_rule('/', view_func=View.as_view('index'))
c = app.test_client()
assert c.get('/').data == b'GET'
assert c.post('/').status_code == 405
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]
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]
passenv = LANG
usedevelop=true
commands =
# We need to install those after Flask is installed.
pip install -e examples/flaskr
pip install -e examples/minitwit
pip install -e examples/patterns/largerapp
pytest --cov=flask --cov-report html []
deps=
pytest
pytest-cov
usedevelop = true
deps =
pytest>=3
coverage
greenlet
blinker
lowest: Werkzeug==0.7
lowest: Werkzeug==0.9
lowest: Jinja2==2.4
lowest: itsdangerous==0.21
lowest: blinker==1.0
release: blinker
devel: git+https://github.com/pallets/werkzeug.git
devel: git+https://github.com/pallets/jinja.git
devel: git+https://github.com/pallets/itsdangerous.git
devel: git+https://github.com/jek/blinker.git
lowest: Click==4.0
devel: https://github.com/pallets/werkzeug/archive/master.tar.gz
devel: https://github.com/pallets/markupsafe/archive/master.tar.gz
devel: https://github.com/pallets/jinja/archive/master.tar.gz
devel: https://github.com/pallets/itsdangerous/archive/master.tar.gz
devel: https://github.com/pallets/click/archive/master.tar.gz
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
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