Merge branch 'master' into improve-flaskr
This commit is contained in:
commit
e6740ec2f3
65 changed files with 3502 additions and 2282 deletions
11
.coveragerc
Normal file
11
.coveragerc
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[run]
|
||||
branch = True
|
||||
source =
|
||||
flask
|
||||
tests
|
||||
|
||||
[paths]
|
||||
source =
|
||||
flask
|
||||
.tox/*/lib/python*/site-packages/flask
|
||||
.tox/pypy/site-packages/flask
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "docs/_themes"]
|
||||
path = docs/_themes
|
||||
url = https://github.com/mitsuhiko/flask-sphinx-themes.git
|
||||
89
.travis.yml
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
|
||||
|
|
|
|||
1
AUTHORS
1
AUTHORS
|
|
@ -21,6 +21,7 @@ Patches and Suggestions
|
|||
- Florent Xicluna
|
||||
- Georg Brandl
|
||||
- Jeff Widman @jeffwidman
|
||||
- Joshua Bronson @jab
|
||||
- Justin Quick
|
||||
- Kenneth Reitz
|
||||
- Keyan Pishdadian
|
||||
|
|
|
|||
53
CHANGES
53
CHANGES
|
|
@ -14,6 +14,57 @@ Major release, unreleased
|
|||
- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify()
|
||||
method returns compressed response by default, and pretty response in
|
||||
debug mode.
|
||||
- Change Flask.__init__ to accept two new keyword arguments, ``host_matching``
|
||||
and ``static_host``. This enables ``host_matching`` to be set properly by the
|
||||
time the constructor adds the static route, and enables the static route to
|
||||
be properly associated with the required host. (``#1559``)
|
||||
- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_)
|
||||
- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``.
|
||||
(`#2017`_)
|
||||
- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable
|
||||
adding OPTIONS method when the ``view_func`` argument is not a class.
|
||||
(`#1489`_).
|
||||
- ``MethodView`` can inherit method handlers from base classes. (`#1936`_)
|
||||
- Errors caused while opening the session at the beginning of the request are
|
||||
handled by the app's error handlers. (`#2254`_)
|
||||
- Blueprints gained ``json_encoder`` and ``json_decoder`` attributes to
|
||||
override the app's encoder and decoder. (`#1898`_)
|
||||
- ``Flask.make_response`` raises ``TypeError`` instead of ``ValueError`` for
|
||||
bad response types. The error messages have been improved to describe why the
|
||||
type is invalid. (`#2256`_)
|
||||
- Add ``routes`` CLI command to output routes registered on the application.
|
||||
(`#2259`_)
|
||||
- Show warning when session cookie domain is a bare hostname or an IP
|
||||
address, as these may not behave properly in some browsers, such as Chrome.
|
||||
(`#2282`_)
|
||||
- Allow IP address as exact session cookie domain. (`#2282`_)
|
||||
- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``.
|
||||
(`#2282`_)
|
||||
- Auto-detect zero-argument app factory called ``create_app`` or ``make_app``
|
||||
from ``FLASK_APP``. (`#2297`_)
|
||||
- Factory functions are not required to take a ``script_info`` parameter to
|
||||
work with the ``flask`` command. If they take a single parameter or a
|
||||
parameter named ``script_info``, the ``ScriptInfo`` object will be passed.
|
||||
(`#2319`_)
|
||||
|
||||
.. _#1489: https://github.com/pallets/flask/pull/1489
|
||||
.. _#1898: https://github.com/pallets/flask/pull/1898
|
||||
.. _#1936: https://github.com/pallets/flask/pull/1936
|
||||
.. _#2017: https://github.com/pallets/flask/pull/2017
|
||||
.. _#2223: https://github.com/pallets/flask/pull/2223
|
||||
.. _#2254: https://github.com/pallets/flask/pull/2254
|
||||
.. _#2256: https://github.com/pallets/flask/pull/2256
|
||||
.. _#2259: https://github.com/pallets/flask/pull/2259
|
||||
.. _#2282: https://github.com/pallets/flask/pull/2282
|
||||
.. _#2297: https://github.com/pallets/flask/pull/2297
|
||||
.. _#2319: https://github.com/pallets/flask/pull/2319
|
||||
|
||||
Version 0.12.2
|
||||
--------------
|
||||
|
||||
Released on May 16 2017
|
||||
|
||||
- Fix a bug in `safe_join` on Windows.
|
||||
|
||||
Version 0.12.1
|
||||
--------------
|
||||
|
|
@ -133,6 +184,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``).
|
||||
|
|
|
|||
|
|
@ -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,9 +88,19 @@ 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
|
||||
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
|
||||
|
|
@ -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
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
Normal file
BIN
docs/_static/pycharm-runconfig.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
2
docs/_templates/sidebarintro.html
vendored
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 +0,0 @@
|
|||
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9
|
||||
|
|
@ -177,11 +177,11 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
|
|||
admin = Blueprint('admin', __name__, template_folder='templates')
|
||||
|
||||
For static files, the path can be absolute or relative to the blueprint
|
||||
resource folder.
|
||||
resource folder.
|
||||
|
||||
The template folder is added to the search path of templates but with a lower
|
||||
priority than the actual application's template folder. That way you can
|
||||
easily override templates that a blueprint provides in the actual application.
|
||||
The template folder is added to the search path of templates but with a lower
|
||||
priority than the actual application's template folder. That way you can
|
||||
easily override templates that a blueprint provides in the actual application.
|
||||
This also means that if you don't want a blueprint template to be accidentally
|
||||
overridden, make sure that no other blueprint or actual application template
|
||||
has the same relative path. When multiple blueprints provide the same relative
|
||||
|
|
@ -194,7 +194,7 @@ want to render the template ``'admin/index.html'`` and you have provided
|
|||
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
|
||||
for the extra ``admin`` folder is to avoid getting our template overridden
|
||||
by a template named ``index.html`` in the actual application template
|
||||
folder.
|
||||
folder.
|
||||
|
||||
To further reiterate this: if you have a blueprint named ``admin`` and you
|
||||
want to render a template called :file:`index.html` which is specific to this
|
||||
|
|
@ -245,4 +245,22 @@ Here is an example for a "404 Page Not Found" exception::
|
|||
def page_not_found(e):
|
||||
return render_template('pages/404.html')
|
||||
|
||||
Most errorhandlers will simply work as expected; however, there is a caveat
|
||||
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||
access. This is because the blueprint does not "own" a certain URL space, so
|
||||
the application instance has no way of knowing which blueprint errorhandler it
|
||||
should run if given an invalid URL. If you would like to execute different
|
||||
handling strategies for these errors based on URL prefixes, they may be defined
|
||||
at the application level using the ``request`` proxy object::
|
||||
|
||||
@app.errorhandler(404)
|
||||
@app.errorhandler(405)
|
||||
def _handle_api_error(ex):
|
||||
if request.path.startswith('/api/'):
|
||||
return jsonify_error(ex)
|
||||
else:
|
||||
return ex
|
||||
|
||||
More information on error handling see :ref:`errorpages`.
|
||||
|
|
|
|||
55
docs/cli.rst
55
docs/cli.rst
|
|
@ -56,6 +56,18 @@ If you are constantly working with a virtualenv you can also put the
|
|||
bottom of the file. That way every time you activate your virtualenv you
|
||||
automatically also activate the correct application name.
|
||||
|
||||
Edit the activate script for the shell you use. For example:
|
||||
|
||||
Unix Bash: ``venv/bin/activate``::
|
||||
|
||||
FLASK_APP=hello
|
||||
export FLASK_APP
|
||||
|
||||
Windows CMD.exe: ``venv\Scripts\activate.bat``::
|
||||
|
||||
set "FLASK_APP=hello"
|
||||
:END
|
||||
|
||||
Debug Flag
|
||||
----------
|
||||
|
||||
|
|
@ -246,3 +258,46 @@ Inside :file:`mypackage/commands.py` you can then export a Click object::
|
|||
Once that package is installed in the same virtualenv as Flask itself you
|
||||
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``.
|
||||
|
|
|
|||
29
docs/conf.py
29
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 -----------------------------------------------------
|
||||
|
|
@ -38,6 +37,14 @@ extensions = [
|
|||
'flaskdocext'
|
||||
]
|
||||
|
||||
try:
|
||||
__import__('sphinxcontrib.log_cabinet')
|
||||
except ImportError:
|
||||
print('sphinxcontrib-log-cabinet is not installed.')
|
||||
print('Changelog directives will not be re-organized.')
|
||||
else:
|
||||
extensions.append('sphinxcontrib.log_cabinet')
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
|
|
@ -113,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".
|
||||
|
|
@ -265,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():
|
||||
|
|
|
|||
|
|
@ -132,13 +132,13 @@ The following configuration values are used internally by Flask:
|
|||
by default enables URL generation
|
||||
without a request context but with an
|
||||
application context.
|
||||
``APPLICATION_ROOT`` If the application does not occupy
|
||||
a whole domain or subdomain this can
|
||||
be set to the path where the application
|
||||
is configured to live. This is for
|
||||
session cookie as path value. If
|
||||
domains are used, this should be
|
||||
``None``.
|
||||
``APPLICATION_ROOT`` The path value used for the session
|
||||
cookie if ``SESSION_COOKIE_PATH`` isn't
|
||||
set. If it's also ``None`` ``'/'`` is used.
|
||||
Note that to actually serve your Flask
|
||||
app under a subpath you need to tell
|
||||
your WSGI container the ``SCRIPT_NAME``
|
||||
WSGI environment variable.
|
||||
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||
reject incoming requests with a
|
||||
content length greater than this by
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ Self-hosted options
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
mod_wsgi
|
||||
wsgi-standalone
|
||||
uwsgi
|
||||
mod_wsgi
|
||||
fastcgi
|
||||
cgi
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
|
|
@ -3,163 +3,173 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Flask depends on some external libraries, like `Werkzeug
|
||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_.
|
||||
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
||||
applications and a variety of servers for both development and deployment.
|
||||
Jinja2 renders templates.
|
||||
Python Version
|
||||
--------------
|
||||
|
||||
So how do you get all that on your computer quickly? There are many ways you
|
||||
could do that, but the most kick-ass method is virtualenv, so let's have a look
|
||||
at that first.
|
||||
We recommend using the latest version of Python 3. Flask supports Python 3.3
|
||||
and newer, Python 2.6 and newer, and PyPy.
|
||||
|
||||
You will need Python 2.6 or newer to get started, so be sure to have an
|
||||
up-to-date Python 2.x installation. For using Flask with Python 3 have a
|
||||
look at :ref:`python3-support`.
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
.. _virtualenv:
|
||||
These distributions will be installed automatically when installing Flask.
|
||||
|
||||
virtualenv
|
||||
----------
|
||||
* `Werkzeug`_ implements WSGI, the standard Python interface between
|
||||
applications and servers.
|
||||
* `Jinja`_ is a template language that renders the pages your application
|
||||
serves.
|
||||
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
|
||||
templates to avoid injection attacks.
|
||||
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
|
||||
to protect Flask's session cookie.
|
||||
* `Click`_ is a framework for writing command line applications. It provides
|
||||
the ``flask`` command and allows adding custom management commands.
|
||||
|
||||
Virtualenv is probably what you want to use during development, and if you have
|
||||
shell access to your production machines, you'll probably want to use it there,
|
||||
too.
|
||||
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||
.. _Jinja: http://jinja.pocoo.org/
|
||||
.. _MarkupSafe: https://pypi.python.org/pypi/MarkupSafe
|
||||
.. _ItsDangerous: https://pythonhosted.org/itsdangerous/
|
||||
.. _Click: http://click.pocoo.org/
|
||||
|
||||
What problem does virtualenv solve? If you like Python as much as I do,
|
||||
chances are you want to use it for other projects besides Flask-based web
|
||||
applications. But the more projects you have, the more likely it is that you
|
||||
will be working with different versions of Python itself, or at least different
|
||||
versions of Python libraries. Let's face it: quite often libraries break
|
||||
backwards compatibility, and it's unlikely that any serious application will
|
||||
have zero dependencies. So what do you do if two or more of your projects have
|
||||
conflicting dependencies?
|
||||
Optional dependencies
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Virtualenv to the rescue! Virtualenv enables multiple side-by-side
|
||||
installations of Python, one for each project. It doesn't actually install
|
||||
separate copies of Python, but it does provide a clever way to keep different
|
||||
project environments isolated. Let's see how virtualenv works.
|
||||
These distributions will not be installed automatically. Flask will detect and
|
||||
use them if you install them.
|
||||
|
||||
If you are on Mac OS X or Linux, chances are that the following
|
||||
command will work for you::
|
||||
* `Blinker`_ provides support for :ref:`signals`.
|
||||
* `SimpleJSON`_ is a fast JSON implementation that is compatible with
|
||||
Python's ``json`` module. It is preferred for JSON operations if it is
|
||||
installed.
|
||||
|
||||
$ sudo pip install virtualenv
|
||||
.. _Blinker: https://pythonhosted.org/blinker/
|
||||
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
||||
|
||||
It will probably install virtualenv on your system. Maybe it's even
|
||||
in your package manager. If you use Ubuntu, try::
|
||||
Virtual environments
|
||||
--------------------
|
||||
|
||||
$ sudo apt-get install python-virtualenv
|
||||
Use a virtual environment to manage the dependencies for your project, both in
|
||||
development and in production.
|
||||
|
||||
If you are on Windows and don't have the ``easy_install`` command, you must
|
||||
install it first. Check the :ref:`windows-easy-install` section for more
|
||||
information about how to do that. Once you have it installed, run the same
|
||||
commands as above, but without the ``sudo`` prefix.
|
||||
What problem does a virtual environment solve? The more Python projects you
|
||||
have, the more likely it is that you need to work with different versions of
|
||||
Python libraries, or even Python itself. Newer versions of libraries for one
|
||||
project can break compatibility in another project.
|
||||
|
||||
Once you have virtualenv installed, just fire up a shell and create
|
||||
your own environment. I usually create a project folder and a :file:`venv`
|
||||
folder within::
|
||||
Virtual environments are independent groups of Python libraries, one for each
|
||||
project. Packages installed for one project will not affect other projects or
|
||||
the operating system's packages.
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
$ virtualenv venv
|
||||
New python executable in venv/bin/python
|
||||
Installing setuptools, pip............done.
|
||||
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
||||
environments. If you're using a modern version of Python, you can continue on
|
||||
to the next section.
|
||||
|
||||
Now, whenever you want to work on a project, you only have to activate the
|
||||
corresponding environment. On OS X and Linux, do the following::
|
||||
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
||||
|
||||
$ . venv/bin/activate
|
||||
.. _install-create-env:
|
||||
|
||||
If you are a Windows user, the following command is for you::
|
||||
Create an environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
$ venv\Scripts\activate
|
||||
Create a project folder and a :file:`venv` folder within:
|
||||
|
||||
Either way, you should now be using your virtualenv (notice how the prompt of
|
||||
your shell has changed to show the active environment).
|
||||
.. code-block:: sh
|
||||
|
||||
And if you want to go back to the real world, use the following command::
|
||||
mkdir myproject
|
||||
cd myproject
|
||||
python3 -m venv venv
|
||||
|
||||
$ deactivate
|
||||
On Windows:
|
||||
|
||||
After doing this, the prompt of your shell should be as familiar as before.
|
||||
.. code-block:: bat
|
||||
|
||||
Now, let's move on. Enter the following command to get Flask activated in your
|
||||
virtualenv::
|
||||
py -3 -m venv venv
|
||||
|
||||
$ pip install Flask
|
||||
If you needed to install virtualenv because you are on an older version of
|
||||
Python, use the following command instead:
|
||||
|
||||
A few seconds later and you are good to go.
|
||||
.. code-block:: sh
|
||||
|
||||
virtualenv venv
|
||||
|
||||
System-Wide Installation
|
||||
------------------------
|
||||
On Windows:
|
||||
|
||||
This is possible as well, though I do not recommend it. Just run
|
||||
``pip`` with root privileges::
|
||||
.. code-block:: bat
|
||||
|
||||
$ sudo pip install Flask
|
||||
\Python27\Scripts\virtualenv.exe venv
|
||||
|
||||
(On Windows systems, run it in a command-prompt window with administrator
|
||||
privileges, and leave out ``sudo``.)
|
||||
Activate the environment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before you work on your project, activate the corresponding environment:
|
||||
|
||||
Living on the Edge
|
||||
.. code-block:: sh
|
||||
|
||||
. venv/bin/activate
|
||||
|
||||
On Windows:
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
venv\Scripts\activate
|
||||
|
||||
Your shell prompt will change to show the name of the activated environment.
|
||||
|
||||
Install Flask
|
||||
-------------
|
||||
|
||||
Within the activated environment, use the following command to install Flask:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install Flask
|
||||
|
||||
Living on the edge
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to work with the latest Flask code before it's released, install or
|
||||
update the code from the master branch:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install -U https://github.com/pallets/flask/archive/master.tar.gz
|
||||
|
||||
.. _install-install-virtualenv:
|
||||
|
||||
Install virtualenv
|
||||
------------------
|
||||
|
||||
If you want to work with the latest version of Flask, there are two ways: you
|
||||
can either let ``pip`` pull in the development version, or you can tell
|
||||
it to operate on a git checkout. Either way, virtualenv is recommended.
|
||||
If you are using Python 2, the venv module is not available. Instead,
|
||||
install `virtualenv`_.
|
||||
|
||||
Get the git checkout in a new virtualenv and run in development mode::
|
||||
On Linux, virtualenv is provided by your package manager:
|
||||
|
||||
$ git clone https://github.com/pallets/flask.git
|
||||
Initialized empty Git repository in ~/dev/flask/.git/
|
||||
$ cd flask
|
||||
$ virtualenv venv
|
||||
New python executable in venv/bin/python
|
||||
Installing setuptools, pip............done.
|
||||
$ . venv/bin/activate
|
||||
$ python setup.py develop
|
||||
...
|
||||
Finished processing dependencies for Flask
|
||||
.. code-block:: sh
|
||||
|
||||
This will pull in the dependencies and activate the git head as the current
|
||||
version inside the virtualenv. Then all you have to do is run ``git pull
|
||||
origin`` to update to the latest version.
|
||||
# Debian, Ubuntu
|
||||
sudo apt-get install python-virtualenv
|
||||
|
||||
.. _windows-easy-install:
|
||||
# CentOS, Fedora
|
||||
sudo yum install python-virtualenv
|
||||
|
||||
`pip` and `setuptools` on Windows
|
||||
---------------------------------
|
||||
# Arch
|
||||
sudo pacman -S python-virtualenv
|
||||
|
||||
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools``
|
||||
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial
|
||||
package you will need is pip - this will let you install
|
||||
anything else (like virtualenv). Fortunately there is a "bootstrap script"
|
||||
you can run to install.
|
||||
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
|
||||
|
||||
If you don't currently have ``pip``, then `get-pip.py` will install it for you.
|
||||
.. code-block:: sh
|
||||
|
||||
`get-pip.py`_
|
||||
sudo python2 Downloads/get-pip.py
|
||||
sudo python2 -m pip install virtualenv
|
||||
|
||||
It should be double-clickable once you download it. If you already have ``pip``,
|
||||
you can upgrade them by running::
|
||||
On Windows, as an administrator:
|
||||
|
||||
> pip install --upgrade pip setuptools
|
||||
.. code-block:: bat
|
||||
|
||||
Most often, once you pull up a command prompt you want to be able to type ``pip``
|
||||
and ``python`` which will run those things, but this might not automatically happen
|
||||
on Windows, because it doesn't know where those executables are (give either a try!).
|
||||
\Python27\python.exe Downloads\get-pip.py
|
||||
\Python27\python.exe -m pip install virtualenv
|
||||
|
||||
To fix this, you should be able to navigate to your Python install directory
|
||||
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
|
||||
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
|
||||
check that you can now just type ``python`` to bring up the interpreter.
|
||||
|
||||
Finally, to install `virtualenv`_, you can simply run::
|
||||
|
||||
> pip install virtualenv
|
||||
|
||||
Then you can be off on your way following the installation instructions above.
|
||||
Now you can continue to :ref:`install-create-env`.
|
||||
|
||||
.. _virtualenv: https://virtualenv.pypa.io/
|
||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -3,71 +3,43 @@
|
|||
Deferred Request Callbacks
|
||||
==========================
|
||||
|
||||
One of the design principles of Flask is that response objects are created
|
||||
and passed down a chain of potential callbacks that can modify them or
|
||||
replace them. When the request handling starts, there is no response
|
||||
object yet. It is created as necessary either by a view function or by
|
||||
some other component in the system.
|
||||
One of the design principles of Flask is that response objects are created and
|
||||
passed down a chain of potential callbacks that can modify them or replace
|
||||
them. When the request handling starts, there is no response object yet. It is
|
||||
created as necessary either by a view function or by some other component in
|
||||
the system.
|
||||
|
||||
But what happens if you want to modify the response at a point where the
|
||||
response does not exist yet? A common example for that would be a
|
||||
before-request function that wants to set a cookie on the response object.
|
||||
What happens if you want to modify the response at a point where the response
|
||||
does not exist yet? A common example for that would be a
|
||||
:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
|
||||
response object.
|
||||
|
||||
One way is to avoid the situation. Very often that is possible. For
|
||||
instance you can try to move that logic into an after-request callback
|
||||
instead. Sometimes however moving that code there is just not a very
|
||||
pleasant experience or makes code look very awkward.
|
||||
One way is to avoid the situation. Very often that is possible. For instance
|
||||
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
||||
callback instead. However, sometimes moving code there makes it more
|
||||
more complicated or awkward to reason about.
|
||||
|
||||
As an alternative possibility you can attach a bunch of callback functions
|
||||
to the :data:`~flask.g` object and call them at the end of the request.
|
||||
This way you can defer code execution from anywhere in the application.
|
||||
|
||||
|
||||
The Decorator
|
||||
-------------
|
||||
|
||||
The following decorator is the key. It registers a function on a list on
|
||||
the :data:`~flask.g` object::
|
||||
|
||||
from flask import g
|
||||
|
||||
def after_this_request(f):
|
||||
if not hasattr(g, 'after_request_callbacks'):
|
||||
g.after_request_callbacks = []
|
||||
g.after_request_callbacks.append(f)
|
||||
return f
|
||||
|
||||
|
||||
Calling the Deferred
|
||||
--------------------
|
||||
|
||||
Now you can use the `after_this_request` decorator to mark a function to
|
||||
be called at the end of the request. But we still need to call them. For
|
||||
this the following function needs to be registered as
|
||||
:meth:`~flask.Flask.after_request` callback::
|
||||
|
||||
@app.after_request
|
||||
def call_after_request_callbacks(response):
|
||||
for callback in getattr(g, 'after_request_callbacks', ()):
|
||||
callback(response)
|
||||
return response
|
||||
|
||||
|
||||
A Practical Example
|
||||
-------------------
|
||||
As an alternative, you can use :func:`~flask.after_this_request` to register
|
||||
callbacks that will execute after only the current request. This way you can
|
||||
defer code execution from anywhere in the application, based on the current
|
||||
request.
|
||||
|
||||
At any time during a request, we can register a function to be called at the
|
||||
end of the request. For example you can remember the current language of the
|
||||
user in a cookie in the before-request function::
|
||||
end of the request. For example you can remember the current language of the
|
||||
user in a cookie in a :meth:`~flask.Flask.before_request` callback::
|
||||
|
||||
from flask import request
|
||||
from flask import request, after_this_request
|
||||
|
||||
@app.before_request
|
||||
def detect_user_language():
|
||||
language = request.cookies.get('user_lang')
|
||||
|
||||
if language is None:
|
||||
language = guess_language_from_request()
|
||||
|
||||
# when the response exists, set a cookie with the language
|
||||
@after_this_request
|
||||
def remember_language(response):
|
||||
response.set_cookie('user_lang', language)
|
||||
|
||||
g.language = language
|
||||
|
|
|
|||
|
|
@ -47,37 +47,53 @@ even if the application behaves correctly:
|
|||
Error Handlers
|
||||
--------------
|
||||
|
||||
An error handler is a function, just like a view function, but it is
|
||||
called when an error happens and is passed that error. The error is most
|
||||
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
||||
can be a different error: a handler for internal server errors will be
|
||||
passed other exception instances as well if they are uncaught.
|
||||
An error handler is a function that returns a response when a type of error is
|
||||
raised, similar to how a view is a function that returns a response when a
|
||||
request URL is matched. It is passed the instance of the error being handled,
|
||||
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
|
||||
handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
||||
addition to explicit 500 errors.
|
||||
|
||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||
decorator and the error code of the exception. Keep in mind that Flask
|
||||
will *not* set the error code for you, so make sure to also provide the
|
||||
HTTP status code when returning a response.
|
||||
decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
|
||||
can be registered for a status code, like 404, or for an exception class.
|
||||
|
||||
Please note that if you add an error handler for "500 Internal Server
|
||||
Error", Flask will not trigger it if it's running in Debug mode.
|
||||
The status code of the response will not be set to the handler's code. Make
|
||||
sure to provide the appropriate HTTP status code when returning a response from
|
||||
a handler.
|
||||
|
||||
Here an example implementation for a "404 Page Not Found" exception::
|
||||
A handler for "500 Internal Server Error" will not be used when running in
|
||||
debug mode. Instead, the interactive debugger will be shown.
|
||||
|
||||
Here is an example implementation for a "404 Page Not Found" exception::
|
||||
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
# note that we set the 404 status explicitly
|
||||
return render_template('404.html'), 404
|
||||
|
||||
When using the :ref:`application factory pattern <app-factories>`::
|
||||
|
||||
from flask import Flask, render_template
|
||||
|
||||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
def create_app(config_filename):
|
||||
app = Flask(__name__)
|
||||
app.register_error_handler(404, page_not_found)
|
||||
return app
|
||||
|
||||
An example template might be this:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Page Not Found{% endblock %}
|
||||
{% block body %}
|
||||
<h1>Page Not Found</h1>
|
||||
<p>What you were looking for is just not there.
|
||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ specific upload folder and displays a file to the user. Let's look at the
|
|||
bootstrapping code for our application::
|
||||
|
||||
import os
|
||||
from flask import Flask, request, redirect, url_for
|
||||
from flask import Flask, flash, request, redirect, url_for
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ that tells Flask where to find the application instance::
|
|||
export FLASK_APP=yourapplication
|
||||
|
||||
If you are outside of the project directory make sure to provide the exact
|
||||
path to your application directory. Similiarly you can turn on "debug
|
||||
path to your application directory. Similarly you can turn on "debug
|
||||
mode" with this environment variable::
|
||||
|
||||
export FLASK_DEBUG=true
|
||||
|
|
|
|||
|
|
@ -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,20 +160,22 @@ Screenshot of the debugger in action:
|
|||
:class: screenshot
|
||||
:alt: screenshot of debugger in action
|
||||
|
||||
More information on using the debugger can be found in the `Werkzeug
|
||||
documentation`_.
|
||||
|
||||
.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/debug/#using-the-debugger
|
||||
|
||||
Have another debugger in mind? See :ref:`working-with-debuggers`.
|
||||
|
||||
|
||||
Routing
|
||||
-------
|
||||
|
||||
Modern web applications have beautiful URLs. This helps people remember
|
||||
the URLs, which is especially handy for applications that are used from
|
||||
mobile devices with slower network connections. If the user can directly
|
||||
go to the desired page without having to hit the index page it is more
|
||||
likely they will like the page and come back next time.
|
||||
Modern web applications use meaningful URLs to help users. Users are more
|
||||
likely to like a page and come back if the page uses a meaningful URL they can
|
||||
remember and use to directly visit a page.
|
||||
|
||||
As you have seen above, the :meth:`~flask.Flask.route` decorator is used to
|
||||
bind a function to a URL. Here are some basic examples::
|
||||
Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
|
|
@ -176,16 +185,16 @@ bind a function to a URL. Here are some basic examples::
|
|||
def hello():
|
||||
return 'Hello, World'
|
||||
|
||||
But there is more to it! You can make certain parts of the URL dynamic and
|
||||
attach multiple rules to a function.
|
||||
You can do more! You can make parts of the URL dynamic and attach multiple
|
||||
rules to a function.
|
||||
|
||||
Variable Rules
|
||||
``````````````
|
||||
|
||||
To add variable parts to a URL you can mark these special sections as
|
||||
``<variable_name>``. Such a part is then passed as a keyword argument to your
|
||||
function. Optionally a converter can be used by specifying a rule with
|
||||
``<converter:variable_name>``. Here are some nice examples::
|
||||
You can add variable sections to a URL by marking sections with
|
||||
``<variable_name>``. Your function then receives the ``<variable_name>``
|
||||
as a keyword argument. Optionally, you can use a converter to specify the type
|
||||
of the argument like ``<converter:variable_name>``. ::
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def show_user_profile(username):
|
||||
|
|
@ -197,175 +206,122 @@ function. Optionally a converter can be used by specifying a rule with
|
|||
# show the post with the given id, the id is an integer
|
||||
return 'Post %d' % post_id
|
||||
|
||||
The following converters exist:
|
||||
@app.route('/path/<path:subpath>')
|
||||
def show_subpath(subpath):
|
||||
# show the subpath after /path/
|
||||
return 'Subpath %s' % subpath
|
||||
|
||||
=========== ===============================================
|
||||
`string` accepts any text without a slash (the default)
|
||||
`int` accepts integers
|
||||
`float` like ``int`` but for floating point values
|
||||
`path` like the default but also accepts slashes
|
||||
`any` matches one of the items provided
|
||||
`uuid` accepts UUID strings
|
||||
=========== ===============================================
|
||||
Converter types:
|
||||
|
||||
.. admonition:: Unique URLs / Redirection Behavior
|
||||
========== ==========================================
|
||||
``string`` (default) accepts any text without a slash
|
||||
``int`` accepts positive integers
|
||||
``float`` accepts positive floating point values
|
||||
``path`` like ``string`` but also accepts slashes
|
||||
``uuid`` accepts UUID strings
|
||||
========== ==========================================
|
||||
|
||||
Flask's URL rules are based on Werkzeug's routing module. The idea
|
||||
behind that module is to ensure beautiful and unique URLs based on
|
||||
precedents laid down by Apache and earlier HTTP servers.
|
||||
Unique URLs / Redirection Behavior
|
||||
``````````````````````````````````
|
||||
|
||||
Take these two rules::
|
||||
Take these two rules::
|
||||
|
||||
@app.route('/projects/')
|
||||
def projects():
|
||||
return 'The project page'
|
||||
@app.route('/projects/')
|
||||
def projects():
|
||||
return 'The project page'
|
||||
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return 'The about page'
|
||||
@app.route('/about')
|
||||
def about():
|
||||
return 'The about page'
|
||||
|
||||
Though they look rather similar, they differ in their use of the trailing
|
||||
slash in the URL *definition*. In the first case, the canonical URL for the
|
||||
``projects`` endpoint has a trailing slash. In that sense, it is similar to
|
||||
a folder on a filesystem. Accessing it without a trailing slash will cause
|
||||
Flask to redirect to the canonical URL with the trailing slash.
|
||||
Though they look similar, they differ in their use of the trailing slash in
|
||||
the URL. In the first case, the canonical URL for the ``projects`` endpoint
|
||||
uses a trailing slash. It's similar to a folder in a file system; if you
|
||||
access the URL without a trailing slash, Flask redirects you to the
|
||||
canonical URL with the trailing slash.
|
||||
|
||||
In the second case, however, the URL is defined without a trailing slash,
|
||||
rather like the pathname of a file on UNIX-like systems. Accessing the URL
|
||||
with a trailing slash will produce a 404 "Not Found" error.
|
||||
|
||||
This behavior allows relative URLs to continue working even if the trailing
|
||||
slash is omitted, consistent with how Apache and other servers work. Also,
|
||||
the URLs will stay unique, which helps search engines avoid indexing the
|
||||
same page twice.
|
||||
In the second case, however, the URL definition lacks a trailing slash,
|
||||
like the pathname of a file on UNIX-like systems. Accessing the URL with a
|
||||
trailing slash produces a 404 “Not Found” error.
|
||||
|
||||
This behavior allows relative URLs to continue working even if the trailing
|
||||
slash is omitted, consistent with how Apache and other servers work. Also,
|
||||
the URLs will stay unique, which helps search engines avoid indexing the
|
||||
same page twice.
|
||||
|
||||
.. _url-building:
|
||||
|
||||
URL Building
|
||||
````````````
|
||||
|
||||
If it can match URLs, can Flask also generate them? Of course it can. To
|
||||
build a URL to a specific function you can use the :func:`~flask.url_for`
|
||||
function. It accepts the name of the function as first argument and a number
|
||||
of keyword arguments, each corresponding to the variable part of the URL rule.
|
||||
Unknown variable parts are appended to the URL as query parameters. Here are
|
||||
some examples::
|
||||
To build a URL to a specific function, use the :func:`~flask.url_for` function.
|
||||
It accepts the name of the function as its first argument and any number of
|
||||
keyword arguments, each corresponding to a variable part of the URL rule.
|
||||
Unknown variable parts are appended to the URL as query parameters.
|
||||
|
||||
Why would you want to build URLs using the URL reversing function
|
||||
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
||||
|
||||
1. Reversing is often more descriptive than hard-coding the URLs.
|
||||
2. You can change your URLs in one go instead of needing to remember to
|
||||
manually change hard-coded URLs.
|
||||
3. URL building handles escaping of special characters and Unicode data
|
||||
transparently.
|
||||
4. If your application is placed outside the URL root, for example, in
|
||||
``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
|
||||
handles that for you.
|
||||
|
||||
For example, here we use the :meth:`~flask.Flask.test_request_context` method
|
||||
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
|
||||
tells Flask to behave as though it's handling a request even while we use a
|
||||
Python shell. See :ref:`context-locals`. ::
|
||||
|
||||
from flask import Flask, url_for
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'index'
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
return 'login'
|
||||
|
||||
@app.route('/user/<username>')
|
||||
def profile(username):
|
||||
return '{}'s profile'.format(username)
|
||||
|
||||
with app.test_request_context():
|
||||
print(url_for('index'))
|
||||
print(url_for('login'))
|
||||
print(url_for('login', next='/'))
|
||||
print(url_for('profile', username='John Doe'))
|
||||
|
||||
>>> from flask import Flask, url_for
|
||||
>>> app = Flask(__name__)
|
||||
>>> @app.route('/')
|
||||
... def index(): pass
|
||||
...
|
||||
>>> @app.route('/login')
|
||||
... def login(): pass
|
||||
...
|
||||
>>> @app.route('/user/<username>')
|
||||
... def profile(username): pass
|
||||
...
|
||||
>>> with app.test_request_context():
|
||||
... print(url_for('index'))
|
||||
... print(url_for('login'))
|
||||
... print(url_for('login', next='/'))
|
||||
... print(url_for('profile', username='John Doe'))
|
||||
...
|
||||
/
|
||||
/login
|
||||
/login?next=/
|
||||
/user/John%20Doe
|
||||
|
||||
(This also uses the :meth:`~flask.Flask.test_request_context` method, explained
|
||||
below. It tells Flask to behave as though it is handling a request, even
|
||||
though we are interacting with it through a Python shell. Have a look at the
|
||||
explanation below. :ref:`context-locals`).
|
||||
|
||||
Why would you want to build URLs using the URL reversing function
|
||||
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
||||
There are three good reasons for this:
|
||||
|
||||
1. Reversing is often more descriptive than hard-coding the URLs. More
|
||||
importantly, it allows you to change URLs in one go, without having to
|
||||
remember to change URLs all over the place.
|
||||
2. URL building will handle escaping of special characters and Unicode
|
||||
data transparently for you, so you don't have to deal with them.
|
||||
3. If your application is placed outside the URL root - say, in
|
||||
``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle
|
||||
that properly for you.
|
||||
|
||||
|
||||
HTTP Methods
|
||||
````````````
|
||||
|
||||
HTTP (the protocol web applications are speaking) knows different methods for
|
||||
accessing URLs. By default, a route only answers to ``GET`` requests, but that
|
||||
can be changed by providing the ``methods`` argument to the
|
||||
:meth:`~flask.Flask.route` decorator. Here are some examples::
|
||||
|
||||
from flask import request
|
||||
Web applications use different HTTP methods when accessing URLs. You should
|
||||
familiarize yourself with the HTTP methods as you work with Flask. By default,
|
||||
a route only answers to ``GET`` requests. You can use the ``methods`` argument
|
||||
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
|
||||
::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
return do_the_login()
|
||||
do_the_login()
|
||||
else:
|
||||
return show_the_login_form()
|
||||
show_the_login_form()
|
||||
|
||||
If ``GET`` is present, ``HEAD`` will be added automatically for you. You
|
||||
don't have to deal with that. It will also make sure that ``HEAD`` requests
|
||||
are handled as the `HTTP RFC`_ (the document describing the HTTP
|
||||
protocol) demands, so you can completely ignore that part of the HTTP
|
||||
specification. Likewise, as of Flask 0.6, ``OPTIONS`` is implemented for you
|
||||
automatically as well.
|
||||
|
||||
You have no idea what an HTTP method is? Worry not, here is a quick
|
||||
introduction to HTTP methods and why they matter:
|
||||
|
||||
The HTTP method (also often called "the verb") tells the server what the
|
||||
client wants to *do* with the requested page. The following methods are
|
||||
very common:
|
||||
|
||||
``GET``
|
||||
The browser tells the server to just *get* the information stored on
|
||||
that page and send it. This is probably the most common method.
|
||||
|
||||
``HEAD``
|
||||
The browser tells the server to get the information, but it is only
|
||||
interested in the *headers*, not the content of the page. An
|
||||
application is supposed to handle that as if a ``GET`` request was
|
||||
received but to not deliver the actual content. In Flask you don't
|
||||
have to deal with that at all, the underlying Werkzeug library handles
|
||||
that for you.
|
||||
|
||||
``POST``
|
||||
The browser tells the server that it wants to *post* some new
|
||||
information to that URL and that the server must ensure the data is
|
||||
stored and only stored once. This is how HTML forms usually
|
||||
transmit data to the server.
|
||||
|
||||
``PUT``
|
||||
Similar to ``POST`` but the server might trigger the store procedure
|
||||
multiple times by overwriting the old values more than once. Now you
|
||||
might be asking why this is useful, but there are some good reasons
|
||||
to do it this way. Consider that the connection is lost during
|
||||
transmission: in this situation a system between the browser and the
|
||||
server might receive the request safely a second time without breaking
|
||||
things. With ``POST`` that would not be possible because it must only
|
||||
be triggered once.
|
||||
|
||||
``DELETE``
|
||||
Remove the information at the given location.
|
||||
|
||||
``OPTIONS``
|
||||
Provides a quick way for a client to figure out which methods are
|
||||
supported by this URL. Starting with Flask 0.6, this is implemented
|
||||
for you automatically.
|
||||
|
||||
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
||||
form can submit to the server are ``GET`` and ``POST``. But with JavaScript
|
||||
and future HTML standards you can use the other methods as well. Furthermore
|
||||
HTTP has become quite popular lately and browsers are no longer the only
|
||||
clients that are using HTTP. For instance, many revision control systems
|
||||
use it.
|
||||
If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
|
||||
and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
|
||||
``OPTIONS`` is automatically implemented for you.
|
||||
|
||||
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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::
|
||||
|
||||
|
|
|
|||
202
docs/testing.rst
202
docs/testing.rst
|
|
@ -5,23 +5,30 @@ Testing Flask Applications
|
|||
|
||||
**Something that is untested is broken.**
|
||||
|
||||
The origin of this quote is unknown and while it is not entirely correct, it is also
|
||||
not far from the truth. Untested applications make it hard to
|
||||
The origin of this quote is unknown and while it is not entirely correct, it
|
||||
is also not far from the truth. Untested applications make it hard to
|
||||
improve existing code and developers of untested applications tend to
|
||||
become pretty paranoid. If an application has automated tests, you can
|
||||
safely make changes and instantly know if anything breaks.
|
||||
|
||||
Flask provides a way to test your application by exposing the Werkzeug
|
||||
test :class:`~werkzeug.test.Client` and handling the context locals for you.
|
||||
You can then use that with your favourite testing solution. In this documentation
|
||||
we will use the :mod:`unittest` package that comes pre-installed with Python.
|
||||
You can then use that with your favourite testing solution.
|
||||
|
||||
In this documentation we will use the `pytest`_ package as the base
|
||||
framework for our tests. You can install it with ``pip``, like so::
|
||||
|
||||
pip install pytest
|
||||
|
||||
.. _pytest:
|
||||
https://pytest.org
|
||||
|
||||
The Application
|
||||
---------------
|
||||
|
||||
First, we need an application to test; we will use the application from
|
||||
the :ref:`tutorial`. If you don't have that application yet, get the
|
||||
sources from `the examples`_.
|
||||
source code from `the examples`_.
|
||||
|
||||
.. _the examples:
|
||||
https://github.com/pallets/flask/tree/master/examples/flaskr/
|
||||
|
|
@ -29,90 +36,91 @@ sources from `the examples`_.
|
|||
The Testing Skeleton
|
||||
--------------------
|
||||
|
||||
In order to test the application, we add a second module
|
||||
(:file:`flaskr_tests.py`) and create a unittest skeleton there::
|
||||
We begin by adding a tests directory under the application root. Then
|
||||
create a Python file to store our tests (:file:`test_flaskr.py`). When we
|
||||
format the filename like ``test_*.py``, it will be auto-discoverable by
|
||||
pytest.
|
||||
|
||||
Next, we create a `pytest fixture`_ called
|
||||
:func:`client` that configures
|
||||
the application for testing and initializes a new database.::
|
||||
|
||||
import os
|
||||
import flaskr
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
import pytest
|
||||
|
||||
def setUp(self):
|
||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
flaskr.app.config['TESTING'] = True
|
||||
self.app = flaskr.app.test_client()
|
||||
with flaskr.app.app_context():
|
||||
flaskr.init_db()
|
||||
from flaskr import flaskr
|
||||
|
||||
def tearDown(self):
|
||||
os.close(self.db_fd)
|
||||
os.unlink(flaskr.app.config['DATABASE'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@pytest.fixture
|
||||
def client():
|
||||
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
flaskr.app.config['TESTING'] = True
|
||||
client = flaskr.app.test_client()
|
||||
|
||||
The code in the :meth:`~unittest.TestCase.setUp` method creates a new test
|
||||
client and initializes a new database. This function is called before
|
||||
each individual test function is run. To delete the database after the
|
||||
test, we close the file and remove it from the filesystem in the
|
||||
:meth:`~unittest.TestCase.tearDown` method. Additionally during setup the
|
||||
``TESTING`` config flag is activated. What it does is disable the error
|
||||
catching during request handling so that you get better error reports when
|
||||
performing test requests against the application.
|
||||
with flaskr.app.app_context():
|
||||
flaskr.init_db()
|
||||
|
||||
This test client will give us a simple interface to the application. We can
|
||||
trigger test requests to the application, and the client will also keep track
|
||||
of cookies for us.
|
||||
yield client
|
||||
|
||||
Because SQLite3 is filesystem-based we can easily use the tempfile module
|
||||
os.close(db_fd)
|
||||
os.unlink(flaskr.app.config['DATABASE'])
|
||||
|
||||
This client fixture will be called by each individual test. It gives us a
|
||||
simple interface to the application, where we can trigger test requests to the
|
||||
application. The client will also keep track of cookies for us.
|
||||
|
||||
During setup, the ``TESTING`` config flag is activated. What
|
||||
this does is disable error catching during request handling, so that
|
||||
you get better error reports when performing test requests against the
|
||||
application.
|
||||
|
||||
Because SQLite3 is filesystem-based, we can easily use the :mod:`tempfile` module
|
||||
to create a temporary database and initialize it. The
|
||||
:func:`~tempfile.mkstemp` function does two things for us: it returns a
|
||||
low-level file handle and a random file name, the latter we use as
|
||||
database name. We just have to keep the `db_fd` around so that we can use
|
||||
the :func:`os.close` function to close the file.
|
||||
|
||||
To delete the database after the test, the fixture closes the file and removes
|
||||
it from the filesystem.
|
||||
|
||||
If we now run the test suite, we should see the following output::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
$ pytest
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 0 tests in 0.000s
|
||||
================ test session starts ================
|
||||
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
||||
collected 0 items
|
||||
|
||||
OK
|
||||
=========== no tests ran in 0.07 seconds ============
|
||||
|
||||
Even though it did not run any actual tests, we already know that our flaskr
|
||||
Even though it did not run any actual tests, we already know that our ``flaskr``
|
||||
application is syntactically valid, otherwise the import would have died
|
||||
with an exception.
|
||||
|
||||
.. _pytest fixture:
|
||||
https://docs.pytest.org/en/latest/fixture.html
|
||||
|
||||
The First Test
|
||||
--------------
|
||||
|
||||
Now it's time to start testing the functionality of the application.
|
||||
Let's check that the application shows "No entries here so far" if we
|
||||
access the root of the application (``/``). To do this, we add a new
|
||||
test method to our class, like this::
|
||||
access the root of the application (``/``). To do this, we add a new
|
||||
test function to :file:`test_flaskr.py`, like this::
|
||||
|
||||
class FlaskrTestCase(unittest.TestCase):
|
||||
def test_empty_db(client):
|
||||
"""Start with a blank database."""
|
||||
|
||||
def setUp(self):
|
||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||
self.app = flaskr.app.test_client()
|
||||
flaskr.init_db()
|
||||
|
||||
def tearDown(self):
|
||||
os.close(self.db_fd)
|
||||
os.unlink(flaskr.app.config['DATABASE'])
|
||||
|
||||
def test_empty_db(self):
|
||||
rv = self.app.get('/')
|
||||
assert b'No entries here so far' in rv.data
|
||||
rv = client.get('/')
|
||||
assert b'No entries here so far' in rv.data
|
||||
|
||||
Notice that our test functions begin with the word `test`; this allows
|
||||
:mod:`unittest` to automatically identify the method as a test to run.
|
||||
`pytest`_ to automatically identify the function as a test to run.
|
||||
|
||||
By using `self.app.get` we can send an HTTP ``GET`` request to the application with
|
||||
By using ``client.get`` we can send an HTTP ``GET`` request to the application with
|
||||
the given path. The return value will be a :class:`~flask.Flask.response_class` object.
|
||||
We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect
|
||||
the return value (as string) from the application. In this case, we ensure that
|
||||
|
|
@ -120,12 +128,15 @@ the return value (as string) from the application. In this case, we ensure that
|
|||
|
||||
Run it again and you should see one passing test::
|
||||
|
||||
$ python flaskr_tests.py
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.034s
|
||||
$ pytest -v
|
||||
|
||||
OK
|
||||
================ test session starts ================
|
||||
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
||||
collected 1 items
|
||||
|
||||
tests/test_flaskr.py::test_empty_db PASSED
|
||||
|
||||
============= 1 passed in 0.10 seconds ==============
|
||||
|
||||
Logging In and Out
|
||||
------------------
|
||||
|
|
@ -136,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(self, username, password):
|
||||
return self.app.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
def login(client, username, password):
|
||||
return client.post('/login', data=dict(
|
||||
username=username,
|
||||
password=password
|
||||
), follow_redirects=True)
|
||||
|
||||
def logout(self):
|
||||
return self.app.get('/logout', follow_redirects=True)
|
||||
|
||||
def logout(client):
|
||||
return client.get('/logout', follow_redirects=True)
|
||||
|
||||
Now we can easily test that logging in and out works and that it fails with
|
||||
invalid credentials. Add this new test to the class::
|
||||
invalid credentials. Add this new test function::
|
||||
|
||||
def test_login_logout(self):
|
||||
rv = self.login('admin', 'default')
|
||||
assert 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
|
||||
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)
|
||||
|
|
@ -181,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
|
||||
|
||||
OK
|
||||
================ test session starts ================
|
||||
rootdir: ./flask/examples/flaskr, inifile: setup.cfg
|
||||
collected 3 items
|
||||
|
||||
tests/test_flaskr.py::test_empty_db PASSED
|
||||
tests/test_flaskr.py::test_login_logout PASSED
|
||||
tests/test_flaskr.py::test_messages PASSED
|
||||
|
||||
============= 3 passed in 0.23 seconds ==============
|
||||
|
||||
For more complex tests with headers and status codes, check out the
|
||||
`MiniTwit Example`_ from the sources which contains a larger test
|
||||
suite.
|
||||
|
||||
|
||||
.. _MiniTwit Example:
|
||||
https://github.com/pallets/flask/tree/master/examples/minitwit/
|
||||
|
||||
|
||||
Other Testing Tricks
|
||||
--------------------
|
||||
|
||||
|
|
@ -208,7 +230,7 @@ temporarily. With this you can access the :class:`~flask.request`,
|
|||
functions. Here is a full example that demonstrates this approach::
|
||||
|
||||
import flask
|
||||
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with app.test_request_context('/?name=Peter'):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
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`:
|
||||
|
||||
.. sourcecode:: python
|
||||
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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
295
flask/app.py
295
flask/app.py
|
|
@ -10,30 +10,30 @@
|
|||
"""
|
||||
import os
|
||||
import sys
|
||||
from threading import Lock
|
||||
from datetime import timedelta
|
||||
from itertools import chain
|
||||
from functools import update_wrapper
|
||||
from itertools import chain
|
||||
from threading import Lock
|
||||
|
||||
from werkzeug.datastructures import ImmutableDict
|
||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
||||
MethodNotAllowed, BadRequest, default_exceptions
|
||||
from werkzeug.datastructures import ImmutableDict, Headers
|
||||
from werkzeug.exceptions import BadRequest, HTTPException, \
|
||||
InternalServerError, MethodNotAllowed, default_exceptions
|
||||
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
||||
|
||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||
locked_cached_property, _endpoint_from_view_func, find_package, \
|
||||
get_debug_flag
|
||||
from . import json, cli
|
||||
from .wrappers import Request, Response
|
||||
from .config import ConfigAttribute, Config
|
||||
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
||||
from .globals import _request_ctx_stack, request, session, g
|
||||
from . import cli, json
|
||||
from ._compat import integer_types, reraise, string_types, text_type
|
||||
from .config import Config, ConfigAttribute
|
||||
from .ctx import AppContext, RequestContext, _AppCtxGlobals
|
||||
from .globals import _request_ctx_stack, g, request, session
|
||||
from .helpers import _PackageBoundObject, \
|
||||
_endpoint_from_view_func, find_package, get_debug_flag, \
|
||||
get_flashed_messages, locked_cached_property, url_for
|
||||
from .sessions import SecureCookieSessionInterface
|
||||
from .signals import appcontext_tearing_down, got_request_exception, \
|
||||
request_finished, request_started, request_tearing_down
|
||||
from .templating import DispatchingJinjaLoader, Environment, \
|
||||
_default_template_ctx_processor
|
||||
from .signals import request_started, request_finished, got_request_exception, \
|
||||
request_tearing_down, appcontext_tearing_down
|
||||
from ._compat import reraise, string_types, text_type, integer_types
|
||||
_default_template_ctx_processor
|
||||
from .wrappers import Request, Response
|
||||
|
||||
# a lock used for logger initialization
|
||||
_logger_lock = Lock()
|
||||
|
|
@ -123,6 +123,9 @@ class Flask(_PackageBoundObject):
|
|||
.. versionadded:: 0.11
|
||||
The `root_path` parameter was added.
|
||||
|
||||
.. versionadded:: 0.13
|
||||
The `host_matching` and `static_host` parameters were added.
|
||||
|
||||
:param import_name: the name of the application package
|
||||
:param static_url_path: can be used to specify a different path for the
|
||||
static files on the web. Defaults to the name
|
||||
|
|
@ -130,6 +133,11 @@ class Flask(_PackageBoundObject):
|
|||
:param static_folder: the folder with static files that should be served
|
||||
at `static_url_path`. Defaults to the ``'static'``
|
||||
folder in the root path of the application.
|
||||
:param host_matching: sets the app's ``url_map.host_matching`` to the given
|
||||
given value. Defaults to False.
|
||||
:param static_host: the host to use when adding the static route. Defaults
|
||||
to None. Required when using ``host_matching=True``
|
||||
with a ``static_folder`` configured.
|
||||
:param template_folder: the folder that contains the templates that should
|
||||
be used by the application. Defaults to
|
||||
``'templates'`` folder in the root path of the
|
||||
|
|
@ -212,7 +220,7 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
#: The testing flag. Set this to ``True`` to enable the test mode of
|
||||
#: 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
|
||||
|
|
@ -337,7 +345,8 @@ class Flask(_PackageBoundObject):
|
|||
session_interface = SecureCookieSessionInterface()
|
||||
|
||||
def __init__(self, import_name, static_path=None, static_url_path=None,
|
||||
static_folder='static', template_folder='templates',
|
||||
static_folder='static', static_host=None,
|
||||
host_matching=False, template_folder='templates',
|
||||
instance_path=None, instance_relative_config=False,
|
||||
root_path=None):
|
||||
_PackageBoundObject.__init__(self, import_name,
|
||||
|
|
@ -404,17 +413,16 @@ class Flask(_PackageBoundObject):
|
|||
#: .. versionadded:: 0.9
|
||||
self.url_build_error_handlers = []
|
||||
|
||||
#: A dictionary with lists of functions that should be called at the
|
||||
#: beginning of the request. The key of the dictionary is the name of
|
||||
#: the blueprint this function is active for, ``None`` for all requests.
|
||||
#: This can for example be used to open database connections or
|
||||
#: getting hold of the currently logged in user. To register a
|
||||
#: function here, use the :meth:`before_request` decorator.
|
||||
#: A dictionary with lists of functions that will be called at the
|
||||
#: beginning of each request. The key of the dictionary is the name of
|
||||
#: the blueprint this function is active for, or ``None`` for all
|
||||
#: requests. To register a function, use the :meth:`before_request`
|
||||
#: decorator.
|
||||
self.before_request_funcs = {}
|
||||
|
||||
#: A lists of functions that should be called at the beginning of the
|
||||
#: first request to this instance. To register a function here, use
|
||||
#: the :meth:`before_first_request` decorator.
|
||||
#: A list of functions that will be called at the beginning of the
|
||||
#: first request to this instance. To register a function, use the
|
||||
#: :meth:`before_first_request` decorator.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
self.before_first_request_funcs = []
|
||||
|
|
@ -446,12 +454,11 @@ class Flask(_PackageBoundObject):
|
|||
#: .. versionadded:: 0.9
|
||||
self.teardown_appcontext_funcs = []
|
||||
|
||||
#: A dictionary with lists of functions that can be used as URL
|
||||
#: value processor functions. Whenever a URL is built these functions
|
||||
#: are called to modify the dictionary of values in place. The key
|
||||
#: ``None`` here is used for application wide
|
||||
#: callbacks, otherwise the key is the name of the blueprint.
|
||||
#: Each of these functions has the chance to modify the dictionary
|
||||
#: A dictionary with lists of functions that are called before the
|
||||
#: :attr:`before_request_funcs` functions. The key of the dictionary is
|
||||
#: the name of the blueprint this function is active for, or ``None``
|
||||
#: for all requests. To register a function, use
|
||||
#: :meth:`url_value_preprocessor`.
|
||||
#:
|
||||
#: .. versionadded:: 0.7
|
||||
self.url_value_preprocessors = {}
|
||||
|
|
@ -525,19 +532,22 @@ class Flask(_PackageBoundObject):
|
|||
#: app.url_map.converters['list'] = ListConverter
|
||||
self.url_map = Map()
|
||||
|
||||
self.url_map.host_matching = host_matching
|
||||
|
||||
# tracks internally if the application already handled at least one
|
||||
# request.
|
||||
self._got_first_request = False
|
||||
self._before_request_lock = Lock()
|
||||
|
||||
# register the static folder for the application. Do that even
|
||||
# if the folder does not exist. First of all it might be created
|
||||
# while the server is running (usually happens during development)
|
||||
# but also because google appengine stores static files somewhere
|
||||
# else when mapped with the .yml file.
|
||||
# Add a static route using the provided static_url_path, static_host,
|
||||
# and static_folder if there is a configured static_folder.
|
||||
# Note we do this without checking if static_folder exists.
|
||||
# For one, it might be created while the server is running (e.g. during
|
||||
# development). Also, Google App Engine stores static files somewhere
|
||||
if self.has_static_folder:
|
||||
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
|
||||
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||
endpoint='static',
|
||||
endpoint='static', host=static_host,
|
||||
view_func=self.send_static_file)
|
||||
|
||||
#: The click command line context for this application. Commands
|
||||
|
|
@ -966,7 +976,7 @@ class Flask(_PackageBoundObject):
|
|||
return iter(self._blueprint_order)
|
||||
|
||||
@setupmethod
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
|
||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||
decorator. If a view_func is provided it will be registered with the
|
||||
endpoint.
|
||||
|
|
@ -1006,6 +1016,10 @@ class Flask(_PackageBoundObject):
|
|||
endpoint
|
||||
:param view_func: the function to call when serving a request to the
|
||||
provided endpoint
|
||||
:param provide_automatic_options: controls whether the ``OPTIONS``
|
||||
method should be added automatically. This can also be controlled
|
||||
by setting the ``view_func.provide_automatic_options = False``
|
||||
before adding the rule.
|
||||
:param options: the options to be forwarded to the underlying
|
||||
:class:`~werkzeug.routing.Rule` object. A change
|
||||
to Werkzeug is handling of method options. methods
|
||||
|
|
@ -1035,8 +1049,9 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
# starting with Flask 0.8 the view_func object can disable and
|
||||
# force-enable the automatic options handling.
|
||||
provide_automatic_options = getattr(view_func,
|
||||
'provide_automatic_options', None)
|
||||
if provide_automatic_options is None:
|
||||
provide_automatic_options = getattr(view_func,
|
||||
'provide_automatic_options', None)
|
||||
|
||||
if provide_automatic_options is None:
|
||||
if 'OPTIONS' not in methods:
|
||||
|
|
@ -1295,11 +1310,13 @@ class Flask(_PackageBoundObject):
|
|||
@setupmethod
|
||||
def before_request(self, f):
|
||||
"""Registers a function to run before each request.
|
||||
|
||||
For example, this can be used to open a database connection, or to load
|
||||
the logged in user from the session.
|
||||
|
||||
The function will be called without any arguments.
|
||||
If the function returns a non-None value, it's handled as
|
||||
if it was the return value from the view and further
|
||||
request handling is stopped.
|
||||
The function will be called without any arguments. If it returns a
|
||||
non-None value, the value is handled as if it was the return value from
|
||||
the view, and further request handling is stopped.
|
||||
"""
|
||||
self.before_request_funcs.setdefault(None, []).append(f)
|
||||
return f
|
||||
|
|
@ -1418,9 +1435,17 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
@setupmethod
|
||||
def url_value_preprocessor(self, f):
|
||||
"""Registers a function as URL value preprocessor for all view
|
||||
functions of the application. It's called before the view functions
|
||||
are called and can modify the url values provided.
|
||||
"""Register a URL value preprocessor function for all view
|
||||
functions in the application. These functions will be called before the
|
||||
:meth:`before_request` functions.
|
||||
|
||||
The function can modify the values captured from the matched url before
|
||||
they are passed to the view. For example, this can be used to pop a
|
||||
common language code value and place it in ``g`` rather than pass it to
|
||||
every view.
|
||||
|
||||
The function is passed the endpoint name and values dict. The return
|
||||
value is ignored.
|
||||
"""
|
||||
self.url_value_preprocessors.setdefault(None, []).append(f)
|
||||
return f
|
||||
|
|
@ -1435,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:
|
||||
|
|
@ -1451,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
|
||||
|
|
@ -1696,62 +1721,106 @@ class Flask(_PackageBoundObject):
|
|||
return False
|
||||
|
||||
def make_response(self, rv):
|
||||
"""Converts the return value from a view function to a real
|
||||
response object that is an instance of :attr:`response_class`.
|
||||
"""Convert the return value from a view function to an instance of
|
||||
:attr:`response_class`.
|
||||
|
||||
The following types are allowed for `rv`:
|
||||
:param rv: the return value from the view function. The view function
|
||||
must return a response. Returning ``None``, or the view ending
|
||||
without returning, is not allowed. The following types are allowed
|
||||
for ``view_rv``:
|
||||
|
||||
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
|
||||
|
||||
======================= ===========================================
|
||||
:attr:`response_class` the object is returned unchanged
|
||||
:class:`str` a response object is created with the
|
||||
string as body
|
||||
:class:`unicode` a response object is created with the
|
||||
string encoded to utf-8 as body
|
||||
a WSGI function the function is called as WSGI application
|
||||
and buffered as response object
|
||||
:class:`tuple` A tuple in the form ``(response, status,
|
||||
headers)`` or ``(response, headers)``
|
||||
where `response` is any of the
|
||||
types defined here, `status` is a string
|
||||
or an integer and `headers` is a list or
|
||||
a dictionary with header values.
|
||||
======================= ===========================================
|
||||
|
||||
:param rv: the return value from the view function
|
||||
``str`` (``unicode`` in Python 2)
|
||||
A response object is created with the string encoded to UTF-8
|
||||
as the body.
|
||||
|
||||
``bytes`` (``str`` in Python 2)
|
||||
A response object is created with the bytes as the body.
|
||||
|
||||
``tuple``
|
||||
Either ``(body, status, headers)``, ``(body, status)``, or
|
||||
``(body, headers)``, where ``body`` is any of the other types
|
||||
allowed here, ``status`` is a string or an integer, and
|
||||
``headers`` is a dictionary or a list of ``(key, value)``
|
||||
tuples. If ``body`` is a :attr:`response_class` instance,
|
||||
``status`` overwrites the exiting value and ``headers`` are
|
||||
extended.
|
||||
|
||||
:attr:`response_class`
|
||||
The object is returned unchanged.
|
||||
|
||||
other :class:`~werkzeug.wrappers.Response` class
|
||||
The object is coerced to :attr:`response_class`.
|
||||
|
||||
:func:`callable`
|
||||
The function is called as a WSGI application. The result is
|
||||
used to create a response object.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Previously a tuple was interpreted as the arguments for the
|
||||
response object.
|
||||
"""
|
||||
status_or_headers = headers = None
|
||||
if isinstance(rv, tuple):
|
||||
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
|
||||
|
||||
status = headers = None
|
||||
|
||||
# unpack tuple returns
|
||||
if isinstance(rv, (tuple, list)):
|
||||
len_rv = len(rv)
|
||||
|
||||
# a 3-tuple is unpacked directly
|
||||
if len_rv == 3:
|
||||
rv, status, headers = rv
|
||||
# decide if a 2-tuple has status or headers
|
||||
elif len_rv == 2:
|
||||
if isinstance(rv[1], (Headers, dict, tuple, list)):
|
||||
rv, headers = rv
|
||||
else:
|
||||
rv, status = rv
|
||||
# other sized tuples are not allowed
|
||||
else:
|
||||
raise TypeError(
|
||||
'The view function did not return a valid response tuple.'
|
||||
' The tuple must have the form (body, status, headers),'
|
||||
' (body, status), or (body, headers).'
|
||||
)
|
||||
|
||||
# the body must not be None
|
||||
if rv is None:
|
||||
raise ValueError('View function did not return a response')
|
||||
|
||||
if isinstance(status_or_headers, (dict, list)):
|
||||
headers, status_or_headers = status_or_headers, None
|
||||
raise TypeError(
|
||||
'The view function did not return a valid response. The'
|
||||
' function either returned None or ended without a return'
|
||||
' statement.'
|
||||
)
|
||||
|
||||
# make sure the body is an instance of the response class
|
||||
if not isinstance(rv, self.response_class):
|
||||
# When we create a response object directly, we let the constructor
|
||||
# set the headers and status. We do this because there can be
|
||||
# some extra logic involved when creating these objects with
|
||||
# specific values (like default content type selection).
|
||||
if isinstance(rv, (text_type, bytes, bytearray)):
|
||||
rv = self.response_class(rv, headers=headers,
|
||||
status=status_or_headers)
|
||||
headers = status_or_headers = None
|
||||
# let the response class set the status and headers instead of
|
||||
# waiting to do it manually, so that the class can handle any
|
||||
# special logic
|
||||
rv = self.response_class(rv, status=status, headers=headers)
|
||||
status = headers = None
|
||||
else:
|
||||
rv = self.response_class.force_type(rv, request.environ)
|
||||
# evaluate a WSGI callable, or coerce a different response
|
||||
# class to the correct type
|
||||
try:
|
||||
rv = self.response_class.force_type(rv, request.environ)
|
||||
except TypeError as e:
|
||||
new_error = TypeError(
|
||||
'{e}\nThe view function did not return a valid'
|
||||
' response. The return type must be a string, tuple,'
|
||||
' Response instance, or WSGI callable, but it was a'
|
||||
' {rv.__class__.__name__}.'.format(e=e, rv=rv)
|
||||
)
|
||||
reraise(TypeError, new_error, sys.exc_info()[2])
|
||||
|
||||
if status_or_headers is not None:
|
||||
if isinstance(status_or_headers, string_types):
|
||||
rv.status = status_or_headers
|
||||
# prefer the status if it was provided
|
||||
if status is not None:
|
||||
if isinstance(status, (text_type, bytes, bytearray)):
|
||||
rv.status = status
|
||||
else:
|
||||
rv.status_code = status_or_headers
|
||||
rv.status_code = status
|
||||
|
||||
# extend existing headers with provided headers
|
||||
if headers:
|
||||
rv.headers.extend(headers)
|
||||
|
||||
|
|
@ -1814,16 +1883,16 @@ class Flask(_PackageBoundObject):
|
|||
raise error
|
||||
|
||||
def preprocess_request(self):
|
||||
"""Called before the actual request dispatching and will
|
||||
call each :meth:`before_request` decorated function, passing no
|
||||
arguments.
|
||||
If any of these functions returns a value, it's handled as
|
||||
if it was the return value from the view and further
|
||||
request handling is stopped.
|
||||
|
||||
This also triggers the :meth:`url_value_preprocessor` functions before
|
||||
the actual :meth:`before_request` functions are called.
|
||||
"""Called before the request is dispatched. Calls
|
||||
:attr:`url_value_preprocessors` registered with the app and the
|
||||
current blueprint (if any). Then calls :attr:`before_request_funcs`
|
||||
registered with the app and the blueprint.
|
||||
|
||||
If any :meth:`before_request` handler returns a non-None value, the
|
||||
value is handled as if it was the return value from the view, and
|
||||
further request handling is stopped.
|
||||
"""
|
||||
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
|
||||
funcs = self.url_value_preprocessors.get(None, ())
|
||||
|
|
@ -1983,10 +2052,10 @@ class Flask(_PackageBoundObject):
|
|||
exception context to start the response
|
||||
"""
|
||||
ctx = self.request_context(environ)
|
||||
ctx.push()
|
||||
error = None
|
||||
try:
|
||||
try:
|
||||
ctx.push()
|
||||
response = self.full_dispatch_request()
|
||||
except Exception as e:
|
||||
error = e
|
||||
|
|
|
|||
|
|
@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject):
|
|||
warn_on_modifications = False
|
||||
_got_registered_once = False
|
||||
|
||||
#: Blueprint local JSON decoder class to use.
|
||||
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
|
||||
json_encoder = None
|
||||
#: Blueprint local JSON decoder class to use.
|
||||
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
|
||||
json_decoder = None
|
||||
|
||||
def __init__(self, name, import_name, static_folder=None,
|
||||
static_url_path=None, template_folder=None,
|
||||
url_prefix=None, subdomain=None, url_defaults=None,
|
||||
|
|
|
|||
124
flask/cli.py
124
flask/cli.py
|
|
@ -12,41 +12,85 @@
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from threading import Lock, Thread
|
||||
from functools import update_wrapper
|
||||
from operator import attrgetter
|
||||
from threading import Lock, Thread
|
||||
|
||||
import click
|
||||
|
||||
from ._compat import iteritems, reraise
|
||||
from .helpers import get_debug_flag
|
||||
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):
|
||||
|
|
@ -78,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:
|
||||
|
|
@ -104,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:
|
||||
|
|
@ -229,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(
|
||||
|
|
@ -237,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
|
||||
|
|
@ -319,6 +363,7 @@ class FlaskGroup(AppGroup):
|
|||
if add_default_commands:
|
||||
self.add_command(run_command)
|
||||
self.add_command(shell_command)
|
||||
self.add_command(routes_command)
|
||||
|
||||
self._loaded_plugin_commands = False
|
||||
|
||||
|
|
@ -484,6 +529,53 @@ def shell_command():
|
|||
code.interact(banner=banner, local=ctx)
|
||||
|
||||
|
||||
@click.command('routes', short_help='Show the routes for the app.')
|
||||
@click.option(
|
||||
'--sort', '-s',
|
||||
type=click.Choice(('endpoint', 'methods', 'rule', 'match')),
|
||||
default='endpoint',
|
||||
help=(
|
||||
'Method to sort routes by. "match" is the order that Flask will match '
|
||||
'routes when dispatching a request.'
|
||||
)
|
||||
)
|
||||
@click.option(
|
||||
'--all-methods',
|
||||
is_flag=True,
|
||||
help="Show HEAD and OPTIONS methods."
|
||||
)
|
||||
@with_appcontext
|
||||
def routes_command(sort, all_methods):
|
||||
"""Show all registered routes with endpoints and methods."""
|
||||
|
||||
rules = list(current_app.url_map.iter_rules())
|
||||
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
|
||||
|
||||
if sort in ('endpoint', 'rule'):
|
||||
rules = sorted(rules, key=attrgetter(sort))
|
||||
elif sort == 'methods':
|
||||
rules = sorted(rules, key=lambda rule: sorted(rule.methods))
|
||||
|
||||
rule_methods = [
|
||||
', '.join(sorted(rule.methods - ignored_methods)) for rule in rules
|
||||
]
|
||||
|
||||
headers = ('Endpoint', 'Methods', 'Rule')
|
||||
widths = (
|
||||
max(len(rule.endpoint) for rule in rules),
|
||||
max(len(methods) for methods in rule_methods),
|
||||
max(len(rule.rule) for rule in rules),
|
||||
)
|
||||
widths = [max(len(h), w) for h, w in zip(headers, widths)]
|
||||
row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths)
|
||||
|
||||
click.echo(row.format(*headers).strip())
|
||||
click.echo(row.format(*('-' * width for width in widths)))
|
||||
|
||||
for rule, methods in zip(rules, rule_methods):
|
||||
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
|
||||
|
||||
|
||||
cli = FlaskGroup(help="""\
|
||||
This shell command acts as general utility script for Flask applications.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import pkgutil
|
||||
import posixpath
|
||||
|
|
@ -17,6 +18,7 @@ import mimetypes
|
|||
from time import time
|
||||
from zlib import adler32
|
||||
from threading import RLock
|
||||
import unicodedata
|
||||
from werkzeug.routing import BuildError
|
||||
from functools import update_wrapper
|
||||
|
||||
|
|
@ -330,6 +332,7 @@ def url_for(endpoint, **values):
|
|||
values['_external'] = external
|
||||
values['_anchor'] = anchor
|
||||
values['_method'] = method
|
||||
values['_scheme'] = scheme
|
||||
return appctx.app.handle_url_build_error(error, endpoint, values)
|
||||
|
||||
if anchor is not None:
|
||||
|
|
@ -477,8 +480,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
.. versionchanged:: 0.12
|
||||
The `attachment_filename` is preferred over `filename` for MIME-type
|
||||
detection.
|
||||
|
||||
.. versionchanged:: 0.13
|
||||
UTF-8 filenames, as specified in `RFC 2231`_, are supported.
|
||||
|
||||
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
|
||||
|
||||
:param filename_or_fp: the filename of the file to send in `latin-1`.
|
||||
:param filename_or_fp: the filename of the file to send.
|
||||
This is relative to the :attr:`~Flask.root_path`
|
||||
if a relative path is specified.
|
||||
Alternatively a file object might be provided in
|
||||
|
|
@ -534,8 +542,19 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
if attachment_filename is None:
|
||||
raise TypeError('filename unavailable, required for '
|
||||
'sending as attachment')
|
||||
headers.add('Content-Disposition', 'attachment',
|
||||
filename=attachment_filename)
|
||||
|
||||
try:
|
||||
attachment_filename = attachment_filename.encode('latin-1')
|
||||
except UnicodeEncodeError:
|
||||
filenames = {
|
||||
'filename': unicodedata.normalize(
|
||||
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
|
||||
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
|
||||
}
|
||||
else:
|
||||
filenames = {'filename': attachment_filename}
|
||||
|
||||
headers.add('Content-Disposition', 'attachment', **filenames)
|
||||
|
||||
if current_app.use_x_sendfile and filename:
|
||||
if file is not None:
|
||||
|
|
@ -619,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):
|
||||
|
|
@ -958,3 +983,38 @@ def total_seconds(td):
|
|||
:rtype: int
|
||||
"""
|
||||
return td.days * 60 * 60 * 24 + td.seconds
|
||||
|
||||
|
||||
def is_ip(value):
|
||||
"""Determine if the given string is an IP address.
|
||||
|
||||
:param value: value to check
|
||||
:type value: str
|
||||
|
||||
:return: True if string is an IP address
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
for family in (socket.AF_INET, socket.AF_INET6):
|
||||
try:
|
||||
socket.inet_pton(family, value)
|
||||
except socket.error:
|
||||
pass
|
||||
else:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def patch_vary_header(response, value):
|
||||
"""Add a value to the ``Vary`` header if it is not already present."""
|
||||
|
||||
header = response.headers.get('Vary', '')
|
||||
headers = [h for h in (h.strip() for h in header.split(',')) if h]
|
||||
lower_value = value.lower()
|
||||
|
||||
if not any(h.lower() == lower_value for h in headers):
|
||||
headers.append(value)
|
||||
|
||||
updated_header = ', '.join(headers)
|
||||
response.headers['Vary'] = updated_header
|
||||
|
|
|
|||
|
|
@ -91,9 +91,16 @@ class JSONDecoder(_json.JSONDecoder):
|
|||
def _dump_arg_defaults(kwargs):
|
||||
"""Inject default arguments for dump functions."""
|
||||
if current_app:
|
||||
kwargs.setdefault('cls', current_app.json_encoder)
|
||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||
kwargs.setdefault(
|
||||
'cls',
|
||||
bp.json_encoder if bp and bp.json_encoder
|
||||
else current_app.json_encoder
|
||||
)
|
||||
|
||||
if not current_app.config['JSON_AS_ASCII']:
|
||||
kwargs.setdefault('ensure_ascii', False)
|
||||
|
||||
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
||||
else:
|
||||
kwargs.setdefault('sort_keys', True)
|
||||
|
|
@ -103,7 +110,12 @@ def _dump_arg_defaults(kwargs):
|
|||
def _load_arg_defaults(kwargs):
|
||||
"""Inject default arguments for load functions."""
|
||||
if current_app:
|
||||
kwargs.setdefault('cls', current_app.json_decoder)
|
||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||
kwargs.setdefault(
|
||||
'cls',
|
||||
bp.json_decoder if bp and bp.json_decoder
|
||||
else current_app.json_decoder
|
||||
)
|
||||
else:
|
||||
kwargs.setdefault('cls', JSONDecoder)
|
||||
|
||||
|
|
@ -236,11 +248,10 @@ def jsonify(*args, **kwargs):
|
|||
Added support for serializing top-level arrays. This introduces a
|
||||
security risk in ancient browsers. See :ref:`json-security` for details.
|
||||
|
||||
This function's response will be pretty printed if it was not requested
|
||||
with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless
|
||||
the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false.
|
||||
Compressed (not pretty) formatting currently means no indents and no
|
||||
spaces after separators.
|
||||
This function's response will be pretty printed if the
|
||||
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
|
||||
Flask app is running in debug mode. Compressed (not pretty) formatting
|
||||
currently means no indents and no spaces after separators.
|
||||
|
||||
.. versionadded:: 0.2
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -9,17 +9,20 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
import hashlib
|
||||
from base64 import b64encode, b64decode
|
||||
import uuid
|
||||
import warnings
|
||||
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
|
||||
|
||||
from itsdangerous import URLSafeTimedSerializer, BadSignature
|
||||
from .helpers import is_ip, total_seconds
|
||||
|
||||
|
||||
class SessionMixin(object):
|
||||
|
|
@ -48,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):
|
||||
|
|
@ -116,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):
|
||||
|
|
@ -200,30 +225,62 @@ class SessionInterface(object):
|
|||
return isinstance(obj, self.null_session_class)
|
||||
|
||||
def get_cookie_domain(self, app):
|
||||
"""Helpful helper method that returns the cookie domain that should
|
||||
be used for the session cookie if session cookies are used.
|
||||
"""Returns the domain that should be set for the session cookie.
|
||||
|
||||
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
|
||||
falls back to detecting the domain based on ``SERVER_NAME``.
|
||||
|
||||
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
|
||||
updated to avoid re-running the logic.
|
||||
"""
|
||||
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
|
||||
return app.config['SESSION_COOKIE_DOMAIN']
|
||||
if app.config['SERVER_NAME'] is not None:
|
||||
# chop off the port which is usually not supported by browsers
|
||||
rv = '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
|
||||
|
||||
# Google chrome does not like cookies set to .localhost, so
|
||||
# we just go with no domain then. Flask documents anyways that
|
||||
# cross domain cookies need a fully qualified domain name
|
||||
if rv == '.localhost':
|
||||
rv = None
|
||||
rv = app.config['SESSION_COOKIE_DOMAIN']
|
||||
|
||||
# If we infer the cookie domain from the server name we need
|
||||
# to check if we are in a subpath. In that case we can't
|
||||
# set a cross domain cookie.
|
||||
if rv is not None:
|
||||
path = self.get_cookie_path(app)
|
||||
if path != '/':
|
||||
rv = rv.lstrip('.')
|
||||
# set explicitly, or cached from SERVER_NAME detection
|
||||
# if False, return None
|
||||
if rv is not None:
|
||||
return rv if rv else None
|
||||
|
||||
return rv
|
||||
rv = app.config['SERVER_NAME']
|
||||
|
||||
# server name not set, cache False to return none next time
|
||||
if not rv:
|
||||
app.config['SESSION_COOKIE_DOMAIN'] = False
|
||||
return None
|
||||
|
||||
# chop off the port which is usually not supported by browsers
|
||||
# remove any leading '.' since we'll add that later
|
||||
rv = rv.rsplit(':', 1)[0].lstrip('.')
|
||||
|
||||
if '.' not in rv:
|
||||
# Chrome doesn't allow names without a '.'
|
||||
# this should only come up with localhost
|
||||
# hack around this by not setting the name, and show a warning
|
||||
warnings.warn(
|
||||
'"{rv}" is not a valid cookie domain, it must contain a ".".'
|
||||
' Add an entry to your hosts file, for example'
|
||||
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
|
||||
)
|
||||
app.config['SESSION_COOKIE_DOMAIN'] = False
|
||||
return None
|
||||
|
||||
ip = is_ip(rv)
|
||||
|
||||
if ip:
|
||||
warnings.warn(
|
||||
'The session cookie domain is an IP address. This may not work'
|
||||
' as intended in some browsers. Add an entry to your hosts'
|
||||
' file, for example "localhost.localdomain", and use that'
|
||||
' instead.'
|
||||
)
|
||||
|
||||
# if this is not an ip and app is mounted at the root, allow subdomain
|
||||
# matching by adding a '.' prefix
|
||||
if self.get_cookie_path(app) == '/' and not ip:
|
||||
rv = '.' + rv
|
||||
|
||||
app.config['SESSION_COOKIE_DOMAIN'] = rv
|
||||
return rv
|
||||
|
||||
def get_cookie_path(self, app):
|
||||
"""Returns the path for which the cookie should be valid. The
|
||||
|
|
@ -257,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``
|
||||
|
|
@ -338,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
|
||||
|
||||
|
|
@ -361,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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,37 +102,39 @@ 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
|
||||
|
||||
|
||||
class MethodViewType(type):
|
||||
"""Metaclass for :class:`MethodView` that determines what methods the view
|
||||
defines.
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, d):
|
||||
super(MethodViewType, cls).__init__(name, bases, d)
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
rv = type.__new__(cls, name, bases, d)
|
||||
if 'methods' not in d:
|
||||
methods = set(rv.methods or [])
|
||||
for key in d:
|
||||
if key in http_method_funcs:
|
||||
methods = set()
|
||||
|
||||
for key in http_method_funcs:
|
||||
if hasattr(cls, key):
|
||||
methods.add(key.upper())
|
||||
# If we have no method at all in there we don't want to
|
||||
# add a method list. (This is for instance the case for
|
||||
# the base class or another subclass of a base method view
|
||||
# that does not introduce new methods).
|
||||
|
||||
# If we have no method at all in there we don't want to add a
|
||||
# method list. This is for instance the case for the base class
|
||||
# or another subclass of a base method view that does not introduce
|
||||
# new methods.
|
||||
if methods:
|
||||
rv.methods = sorted(methods)
|
||||
return rv
|
||||
cls.methods = methods
|
||||
|
||||
|
||||
class MethodView(with_metaclass(MethodViewType, View)):
|
||||
"""Like a regular class-based view but that dispatches requests to
|
||||
particular methods. For instance if you implement a method called
|
||||
:meth:`get` it means it will respond to ``'GET'`` requests and
|
||||
the :meth:`dispatch_request` implementation will automatically
|
||||
forward your request to that. Also :attr:`options` is set for you
|
||||
automatically::
|
||||
"""A class-based view that dispatches request methods to the corresponding
|
||||
class methods. For example, if you implement a ``get`` method, it will be
|
||||
used to handle ``GET`` requests. ::
|
||||
|
||||
class CounterAPI(MethodView):
|
||||
|
||||
def get(self):
|
||||
return session.get('counter', 0)
|
||||
|
||||
|
|
@ -139,11 +144,14 @@ class MethodView(with_metaclass(MethodViewType, View)):
|
|||
|
||||
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
||||
"""
|
||||
|
||||
def dispatch_request(self, *args, **kwargs):
|
||||
meth = getattr(self, request.method.lower(), None)
|
||||
|
||||
# If the request method is HEAD and we don't have a handler for it
|
||||
# retry with GET.
|
||||
if meth is None and request.method == 'HEAD':
|
||||
meth = getattr(self, 'get', None)
|
||||
|
||||
assert meth is not None, 'Unimplemented method %r' % request.method
|
||||
return meth(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -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
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',
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
tox
|
||||
pytest
|
||||
pytest-cov
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1011
tests/test_basic.py
1011
tests/test_basic.py
File diff suppressed because it is too large
Load diff
|
|
@ -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'
|
||||
|
||||
assert c.get('/decorator').data == b'boom'
|
||||
assert c.get('/function').data == b'bam'
|
||||
|
||||
def test_blueprint_url_definitions():
|
||||
def test_blueprint_app_error_handling(app, client):
|
||||
errors = flask.Blueprint('errors', __name__)
|
||||
|
||||
@errors.app_errorhandler(403)
|
||||
def forbidden_handler(e):
|
||||
return 'you shall not pass', 403
|
||||
|
||||
@app.route('/forbidden')
|
||||
def app_forbidden():
|
||||
flask.abort(403)
|
||||
|
||||
forbidden_bp = flask.Blueprint('forbidden_bp', __name__)
|
||||
|
||||
@forbidden_bp.route('/nope')
|
||||
def bp_forbidden():
|
||||
flask.abort(403)
|
||||
|
||||
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,231 +360,461 @@ 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_template_filter():
|
||||
|
||||
def test_endpoint_decorator(app, client):
|
||||
from werkzeug.routing import Rule
|
||||
app.url_map.add(Rule('/foo', endpoint='bar'))
|
||||
|
||||
bp = flask.Blueprint('bp', __name__)
|
||||
|
||||
@bp.endpoint('bar')
|
||||
def foobar():
|
||||
return flask.request.endpoint
|
||||
|
||||
app.register_blueprint(bp, url_prefix='/bp_prefix')
|
||||
|
||||
assert client.get('/foo').data == b'bar'
|
||||
assert client.get('/bp_prefix/bar').status_code == 404
|
||||
|
||||
|
||||
def test_template_filter(app):
|
||||
bp = flask.Blueprint('bp', __name__)
|
||||
|
||||
@bp.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/'
|
||||
|
|
|
|||
|
|
@ -14,17 +14,23 @@
|
|||
from __future__ import absolute_import, print_function
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
import click
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from flask import Flask, current_app
|
||||
|
||||
from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
|
||||
from flask.cli import cli, AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
|
||||
find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \
|
||||
find_default_import_path, get_version
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner():
|
||||
return CliRunner()
|
||||
|
||||
|
||||
def test_cli_name(test_apps):
|
||||
"""Make sure the CLI object's name is the app's name and not the app itself"""
|
||||
from cliapp.app import testapp
|
||||
|
|
@ -33,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):
|
||||
|
|
@ -77,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):
|
||||
|
|
@ -103,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()
|
||||
|
|
@ -129,8 +207,9 @@ def test_scriptinfo(test_apps):
|
|||
assert obj.load_app() == app
|
||||
|
||||
|
||||
def test_with_appcontext():
|
||||
def test_with_appcontext(runner):
|
||||
"""Test of with_appcontext."""
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def testcmd():
|
||||
|
|
@ -138,14 +217,14 @@ def test_with_appcontext():
|
|||
|
||||
obj = ScriptInfo(create_app=lambda info: Flask("testapp"))
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(testcmd, obj=obj)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'testapp\n'
|
||||
|
||||
|
||||
def test_appgroup():
|
||||
def test_appgroup(runner):
|
||||
"""Test of with_appcontext."""
|
||||
|
||||
@click.group(cls=AppGroup)
|
||||
def cli():
|
||||
pass
|
||||
|
|
@ -164,7 +243,6 @@ def test_appgroup():
|
|||
|
||||
obj = ScriptInfo(create_app=lambda info: Flask("testappgroup"))
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['test'], obj=obj)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'testappgroup\n'
|
||||
|
|
@ -174,8 +252,9 @@ def test_appgroup():
|
|||
assert result.output == 'testappgroup\n'
|
||||
|
||||
|
||||
def test_flaskgroup():
|
||||
def test_flaskgroup(runner):
|
||||
"""Test FlaskGroup."""
|
||||
|
||||
def create_app(info):
|
||||
return Flask("flaskgroup")
|
||||
|
||||
|
|
@ -187,14 +266,14 @@ def test_flaskgroup():
|
|||
def test():
|
||||
click.echo(current_app.name)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['test'])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == 'flaskgroup\n'
|
||||
|
||||
|
||||
def test_print_exceptions():
|
||||
def test_print_exceptions(runner):
|
||||
"""Print the stacktrace if the CLI."""
|
||||
|
||||
def create_app(info):
|
||||
raise Exception("oh no")
|
||||
return Flask("flaskgroup")
|
||||
|
|
@ -203,8 +282,65 @@ def test_print_exceptions():
|
|||
def cli(**params):
|
||||
pass
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert result.exit_code == 0
|
||||
assert 'Exception: oh no' in result.output
|
||||
assert 'Traceback' in result.output
|
||||
|
||||
|
||||
class TestRoutes:
|
||||
@pytest.fixture
|
||||
def invoke(self, runner):
|
||||
def create_app(info):
|
||||
app = Flask(__name__)
|
||||
app.testing = True
|
||||
|
||||
@app.route('/get_post/<int:x>/<int:y>', methods=['GET', 'POST'])
|
||||
def yyy_get_post(x, y):
|
||||
pass
|
||||
|
||||
@app.route('/zzz_post', methods=['POST'])
|
||||
def aaa_post():
|
||||
pass
|
||||
|
||||
return app
|
||||
|
||||
cli = FlaskGroup(create_app=create_app)
|
||||
return partial(runner.invoke, cli)
|
||||
|
||||
def expect_order(self, order, output):
|
||||
# skip the header and match the start of each row
|
||||
for expect, line in zip(order, output.splitlines()[2:]):
|
||||
# do this instead of startswith for nicer pytest output
|
||||
assert line[:len(expect)] == expect
|
||||
|
||||
def test_simple(self, invoke):
|
||||
result = invoke(['routes'])
|
||||
assert result.exit_code == 0
|
||||
self.expect_order(
|
||||
['aaa_post', 'static', 'yyy_get_post'],
|
||||
result.output
|
||||
)
|
||||
|
||||
def test_sort(self, invoke):
|
||||
default_output = invoke(['routes']).output
|
||||
endpoint_output = invoke(['routes', '-s', 'endpoint']).output
|
||||
assert default_output == endpoint_output
|
||||
self.expect_order(
|
||||
['static', 'yyy_get_post', 'aaa_post'],
|
||||
invoke(['routes', '-s', 'methods']).output
|
||||
)
|
||||
self.expect_order(
|
||||
['yyy_get_post', 'static', 'aaa_post'],
|
||||
invoke(['routes', '-s', 'rule']).output
|
||||
)
|
||||
self.expect_order(
|
||||
['aaa_post', 'yyy_get_post', 'static'],
|
||||
invoke(['routes', '-s', 'match']).output
|
||||
)
|
||||
|
||||
def test_all_methods(self, invoke):
|
||||
output = invoke(['routes']).output
|
||||
assert 'GET, HEAD, OPTIONS, POST' not in output
|
||||
output = invoke(['routes', '--all-methods']).output
|
||||
assert 'GET, HEAD, OPTIONS, POST' in output
|
||||
|
|
|
|||
|
|
@ -15,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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import pytest
|
||||
|
||||
import flask
|
||||
from flask.sessions import SessionInterface
|
||||
|
||||
try:
|
||||
from greenlet import greenlet
|
||||
|
|
@ -19,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)
|
||||
|
|
@ -32,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)
|
||||
|
|
@ -48,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)
|
||||
|
|
@ -63,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'
|
||||
)
|
||||
|
|
@ -79,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'}):
|
||||
|
|
@ -103,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
|
||||
|
|
@ -118,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()
|
||||
|
|
@ -130,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']
|
||||
|
|
@ -143,53 +147,81 @@ 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 = []
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
reqctx = flask._request_ctx_stack.top.copy()
|
||||
def g():
|
||||
assert not flask.request
|
||||
assert not flask.current_app
|
||||
with reqctx:
|
||||
@pytest.mark.skipif(greenlet is None, reason='greenlet not installed')
|
||||
class TestGreenletContextCopying(object):
|
||||
|
||||
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!'
|
||||
return 42
|
||||
|
||||
rv = app.test_client().get('/?foo=bar')
|
||||
assert rv.data == b'Hello World!'
|
||||
greenlets.append(greenlet(g))
|
||||
return 'Hello World!'
|
||||
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
rv = client.get('/?foo=bar')
|
||||
assert rv.data == b'Hello World!'
|
||||
|
||||
@pytest.mark.skipif(greenlet is None, reason='greenlet not installed')
|
||||
def test_greenlet_context_copying_api():
|
||||
app = flask.Flask(__name__)
|
||||
greenlets = []
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
|
||||
|
||||
def test_session_error_pops_context():
|
||||
class SessionError(Exception):
|
||||
pass
|
||||
|
||||
class FailingSessionInterface(SessionInterface):
|
||||
def open_session(self, app, request):
|
||||
raise SessionError()
|
||||
|
||||
class CustomFlask(flask.Flask):
|
||||
session_interface = FailingSessionInterface()
|
||||
|
||||
app = CustomFlask(__name__)
|
||||
|
||||
@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!'
|
||||
# shouldn't get here
|
||||
assert False
|
||||
|
||||
rv = app.test_client().get('/?foo=bar')
|
||||
assert rv.data == b'Hello World!'
|
||||
|
||||
result = greenlets[0].run()
|
||||
assert result == 42
|
||||
response = app.test_client().get('/')
|
||||
assert response.status_code == 500
|
||||
assert not flask.request
|
||||
assert not flask.current_app
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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'<p>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>') == '<test>'
|
||||
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>') == '<test>'
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
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'
|
||||
|
||||
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'
|
||||
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 = flask.Flask(__name__)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -160,3 +199,41 @@ def test_endpoint_override():
|
|||
|
||||
# But these tests should still pass. We just log a warning.
|
||||
common_test(app)
|
||||
|
||||
|
||||
def test_multiple_inheritance(app, client):
|
||||
class GetView(flask.views.MethodView):
|
||||
def get(self):
|
||||
return 'GET'
|
||||
|
||||
class DeleteView(flask.views.MethodView):
|
||||
def delete(self):
|
||||
return 'DELETE'
|
||||
|
||||
class GetDeleteView(GetView, DeleteView):
|
||||
pass
|
||||
|
||||
app.add_url_rule('/', view_func=GetDeleteView.as_view('index'))
|
||||
|
||||
assert client.get('/').data == b'GET'
|
||||
assert client.delete('/').data == b'DELETE'
|
||||
assert sorted(GetDeleteView.methods) == ['DELETE', 'GET']
|
||||
|
||||
|
||||
def test_remove_method_from_parent(app, client):
|
||||
class GetView(flask.views.MethodView):
|
||||
def get(self):
|
||||
return 'GET'
|
||||
|
||||
class OtherView(flask.views.MethodView):
|
||||
def post(self):
|
||||
return 'POST'
|
||||
|
||||
class View(GetView, OtherView):
|
||||
methods = ['GET']
|
||||
|
||||
app.add_url_rule('/', view_func=View.as_view('index'))
|
||||
|
||||
assert client.get('/').data == b'GET'
|
||||
assert client.post('/').status_code == 405
|
||||
assert sorted(View.methods) == ['GET']
|
||||
|
|
|
|||
76
tox.ini
76
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
|
||||
simplejson: simplejson
|
||||
lowest: Click==4.0
|
||||
|
||||
[testenv:docs]
|
||||
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
|
||||
|
||||
# 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…
Add table
Add a link
Reference in a new issue