forked from orbit-oss/flask
Merge branch 'master' into celery-documentation
This commit is contained in:
commit
f13e3fc352
81 changed files with 1642 additions and 803 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -11,3 +11,9 @@ _mailinglist
|
||||||
.tox
|
.tox
|
||||||
.cache/
|
.cache/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
htmlcov
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
*,cover
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ python:
|
||||||
- "3.3"
|
- "3.3"
|
||||||
- "3.4"
|
- "3.4"
|
||||||
- "3.5"
|
- "3.5"
|
||||||
|
- "3.6"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- REQUIREMENTS=lowest
|
- REQUIREMENTS=lowest
|
||||||
|
|
@ -32,7 +33,10 @@ matrix:
|
||||||
env: REQUIREMENTS=lowest
|
env: REQUIREMENTS=lowest
|
||||||
- python: "3.5"
|
- python: "3.5"
|
||||||
env: REQUIREMENTS=lowest-simplejson
|
env: REQUIREMENTS=lowest-simplejson
|
||||||
|
- python: "3.6"
|
||||||
|
env: REQUIREMENTS=lowest
|
||||||
|
- python: "3.6"
|
||||||
|
env: REQUIREMENTS=lowest-simplejson
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
|
|
|
||||||
1
AUTHORS
1
AUTHORS
|
|
@ -21,6 +21,7 @@ Patches and Suggestions
|
||||||
- Florent Xicluna
|
- Florent Xicluna
|
||||||
- Georg Brandl
|
- Georg Brandl
|
||||||
- Jeff Widman @jeffwidman
|
- Jeff Widman @jeffwidman
|
||||||
|
- Joshua Bronson @jab
|
||||||
- Justin Quick
|
- Justin Quick
|
||||||
- Kenneth Reitz
|
- Kenneth Reitz
|
||||||
- Keyan Pishdadian
|
- Keyan Pishdadian
|
||||||
|
|
|
||||||
67
CHANGES
67
CHANGES
|
|
@ -3,9 +3,74 @@ Flask Changelog
|
||||||
|
|
||||||
Here you can see the full list of changes between each Flask release.
|
Here you can see the full list of changes between each Flask release.
|
||||||
|
|
||||||
|
Version 0.13
|
||||||
|
------------
|
||||||
|
|
||||||
|
Major release, unreleased
|
||||||
|
|
||||||
|
- Make `app.run()` into a noop if a Flask application is run from the
|
||||||
|
development server on the command line. This avoids some behavior that
|
||||||
|
was confusing to debug for newcomers.
|
||||||
|
- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify()
|
||||||
|
method returns compressed response by default, and pretty response in
|
||||||
|
debug mode.
|
||||||
|
- 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`_)
|
||||||
|
|
||||||
|
.. _#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
|
||||||
|
|
||||||
|
Version 0.12.1
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Bugfix release, released on March 31st 2017
|
||||||
|
|
||||||
|
- Prevent `flask run` from showing a NoAppException when an ImportError occurs
|
||||||
|
within the imported application module.
|
||||||
|
- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix
|
||||||
|
``#2118``.
|
||||||
|
- Use the ``SERVER_NAME`` config if it is present as default values for
|
||||||
|
``app.run``. ``#2109``, ``#2152``
|
||||||
|
- Call `ctx.auto_pop` with the exception object instead of `None`, in the
|
||||||
|
event that a `BaseException` such as `KeyboardInterrupt` is raised in a
|
||||||
|
request handler.
|
||||||
|
|
||||||
Version 0.12
|
Version 0.12
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
Released on December 21st 2016, codename Punsch.
|
||||||
|
|
||||||
- the cli command now responds to `--version`.
|
- the cli command now responds to `--version`.
|
||||||
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
|
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
|
||||||
has been removed, as per issue ``#104``. See pull request ``#1849``.
|
has been removed, as per issue ``#104``. See pull request ``#1849``.
|
||||||
|
|
@ -19,6 +84,8 @@ Version 0.12
|
||||||
well as error handlers.
|
well as error handlers.
|
||||||
- Disable logger propagation by default for the app logger.
|
- Disable logger propagation by default for the app logger.
|
||||||
- Add support for range requests in ``send_file``.
|
- Add support for range requests in ``send_file``.
|
||||||
|
- ``app.test_client`` includes preset default environment, which can now be
|
||||||
|
directly set, instead of per ``client.get``.
|
||||||
|
|
||||||
Version 0.11.2
|
Version 0.11.2
|
||||||
--------------
|
--------------
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Submitting patches
|
||||||
clearly under which circumstances the bug happens. Make sure the test fails
|
clearly under which circumstances the bug happens. Make sure the test fails
|
||||||
without your patch.
|
without your patch.
|
||||||
|
|
||||||
- Try to follow `PEP8 <http://legacy.python.org/dev/peps/pep-0008/>`_, but you
|
- Try to follow `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_, but you
|
||||||
may ignore the line-length-limit if following it would make the code uglier.
|
may ignore the line-length-limit if following it would make the code uglier.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ Running the testsuite
|
||||||
You probably want to set up a `virtualenv
|
You probably want to set up a `virtualenv
|
||||||
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
|
||||||
|
|
||||||
The minimal requirement for running the testsuite is ``py.test``. You can
|
The minimal requirement for running the testsuite is ``pytest``. You can
|
||||||
install it with::
|
install it with::
|
||||||
|
|
||||||
pip install pytest
|
pip install pytest
|
||||||
|
|
@ -54,9 +54,9 @@ Install Flask as an editable package using the current source::
|
||||||
|
|
||||||
Then you can run the testsuite with::
|
Then you can run the testsuite with::
|
||||||
|
|
||||||
py.test
|
pytest tests/
|
||||||
|
|
||||||
With only py.test installed, a large part of the testsuite will get skipped
|
With only pytest installed, a large part of the testsuite will get skipped
|
||||||
though. Whether this is relevant depends on which part of Flask you're working
|
though. Whether this is relevant depends on which part of Flask you're working
|
||||||
on. Travis is set up to run the full testsuite when you submit your pull
|
on. Travis is set up to run the full testsuite when you submit your pull
|
||||||
request anyways.
|
request anyways.
|
||||||
|
|
@ -79,11 +79,36 @@ plugin. This assumes you have already run the testsuite (see previous section):
|
||||||
|
|
||||||
After this has been installed, you can output a report to the command line using this command::
|
After this has been installed, you can output a report to the command line using this command::
|
||||||
|
|
||||||
py.test --cov=flask tests/
|
pytest --cov=flask tests/
|
||||||
|
|
||||||
Generate a HTML report can be done using this command::
|
Generate a HTML report can be done using this command::
|
||||||
|
|
||||||
py.test --cov-report html --cov=flask tests/
|
pytest --cov-report html --cov=flask tests/
|
||||||
|
|
||||||
Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io
|
Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io
|
||||||
|
|
||||||
|
Caution
|
||||||
|
=======
|
||||||
|
pushing
|
||||||
|
-------
|
||||||
|
This repository contains several zero-padded file modes that may cause issues when pushing this repository to git hosts other than github. Fixing this is destructive to the commit history, so we suggest ignoring these warnings. If it fails to push and you're using a self-hosted git service like Gitlab, you can turn off repository checks in the admin panel.
|
||||||
|
|
||||||
|
|
||||||
|
cloning
|
||||||
|
-------
|
||||||
|
The zero-padded file modes files above can cause issues while cloning, too. If you have
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[fetch]
|
||||||
|
fsckobjects = true
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[receive]
|
||||||
|
fsckObjects = true
|
||||||
|
|
||||||
|
|
||||||
|
set in your git configuration file, cloning this repository will fail. The only solution is to set both of the above settings to false while cloning, and then setting them back to true after the cloning is finished.
|
||||||
|
|
|
||||||
7
Makefile
7
Makefile
|
|
@ -3,11 +3,8 @@
|
||||||
all: clean-pyc test
|
all: clean-pyc test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
pip install -r test-requirements.txt -q
|
pip install -r test-requirements.txt
|
||||||
FLASK_DEBUG= py.test tests examples
|
tox -e py-release
|
||||||
|
|
||||||
tox-test:
|
|
||||||
tox
|
|
||||||
|
|
||||||
audit:
|
audit:
|
||||||
python setup.py audit
|
python setup.py audit
|
||||||
|
|
|
||||||
4
README
4
README
|
|
@ -33,9 +33,9 @@
|
||||||
|
|
||||||
Good that you're asking. The tests are in the
|
Good that you're asking. The tests are in the
|
||||||
tests/ folder. To run the tests use the
|
tests/ folder. To run the tests use the
|
||||||
`py.test` testing tool:
|
`pytest` testing tool:
|
||||||
|
|
||||||
$ py.test
|
$ pytest
|
||||||
|
|
||||||
Details on contributing can be found in CONTRIBUTING.rst
|
Details on contributing can be found in CONTRIBUTING.rst
|
||||||
|
|
||||||
|
|
|
||||||
6
docs/_templates/sidebarintro.html
vendored
6
docs/_templates/sidebarintro.html
vendored
|
|
@ -16,7 +16,7 @@
|
||||||
<h3>Useful Links</h3>
|
<h3>Useful Links</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="http://flask.pocoo.org/">The Flask Website</a></li>
|
<li><a href="http://flask.pocoo.org/">The Flask Website</a></li>
|
||||||
<li><a href="http://pypi.python.org/pypi/Flask">Flask @ PyPI</a></li>
|
<li><a href="https://pypi.python.org/pypi/Flask">Flask @ PyPI</a></li>
|
||||||
<li><a href="http://github.com/pallets/flask">Flask @ GitHub</a></li>
|
<li><a href="https://github.com/pallets/flask">Flask @ GitHub</a></li>
|
||||||
<li><a href="http://github.com/pallets/flask/issues">Issue Tracker</a></li>
|
<li><a href="https://github.com/pallets/flask/issues">Issue Tracker</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
60
docs/api.rst
60
docs/api.rst
|
|
@ -30,61 +30,12 @@ Incoming Request Data
|
||||||
|
|
||||||
.. autoclass:: Request
|
.. autoclass:: Request
|
||||||
:members:
|
:members:
|
||||||
|
:inherited-members:
|
||||||
.. attribute:: form
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.MultiDict` with the parsed form data from ``POST``
|
|
||||||
or ``PUT`` requests. Please keep in mind that file uploads will not
|
|
||||||
end up here, but instead in the :attr:`files` attribute.
|
|
||||||
|
|
||||||
.. attribute:: args
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.MultiDict` with the parsed contents of the query
|
|
||||||
string. (The part in the URL after the question mark).
|
|
||||||
|
|
||||||
.. attribute:: values
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.CombinedMultiDict` with the contents of both
|
|
||||||
:attr:`form` and :attr:`args`.
|
|
||||||
|
|
||||||
.. attribute:: cookies
|
|
||||||
|
|
||||||
A :class:`dict` with the contents of all cookies transmitted with
|
|
||||||
the request.
|
|
||||||
|
|
||||||
.. attribute:: stream
|
|
||||||
|
|
||||||
If the incoming form data was not encoded with a known mimetype
|
|
||||||
the data is stored unmodified in this stream for consumption. Most
|
|
||||||
of the time it is a better idea to use :attr:`data` which will give
|
|
||||||
you that data as a string. The stream only returns the data once.
|
|
||||||
|
|
||||||
.. attribute:: headers
|
|
||||||
|
|
||||||
The incoming request headers as a dictionary like object.
|
|
||||||
|
|
||||||
.. attribute:: data
|
|
||||||
|
|
||||||
Contains the incoming request data as string in case it came with
|
|
||||||
a mimetype Flask does not handle.
|
|
||||||
|
|
||||||
.. attribute:: files
|
|
||||||
|
|
||||||
A :class:`~werkzeug.datastructures.MultiDict` with files uploaded as part of a
|
|
||||||
``POST`` or ``PUT`` request. Each file is stored as
|
|
||||||
:class:`~werkzeug.datastructures.FileStorage` object. It basically behaves like a
|
|
||||||
standard file object you know from Python, with the difference that
|
|
||||||
it also has a :meth:`~werkzeug.datastructures.FileStorage.save` function that can
|
|
||||||
store the file on the filesystem.
|
|
||||||
|
|
||||||
.. attribute:: environ
|
.. attribute:: environ
|
||||||
|
|
||||||
The underlying WSGI environment.
|
The underlying WSGI environment.
|
||||||
|
|
||||||
.. attribute:: method
|
|
||||||
|
|
||||||
The current request method (``POST``, ``GET`` etc.)
|
|
||||||
|
|
||||||
.. attribute:: path
|
.. attribute:: path
|
||||||
.. attribute:: full_path
|
.. attribute:: full_path
|
||||||
.. attribute:: script_root
|
.. attribute:: script_root
|
||||||
|
|
@ -114,15 +65,8 @@ Incoming Request Data
|
||||||
`url_root` ``u'http://www.example.com/myapplication/'``
|
`url_root` ``u'http://www.example.com/myapplication/'``
|
||||||
============= ======================================================
|
============= ======================================================
|
||||||
|
|
||||||
.. attribute:: is_xhr
|
|
||||||
|
|
||||||
``True`` if the request was triggered via a JavaScript
|
.. attribute:: request
|
||||||
`XMLHttpRequest`. This only works with libraries that support the
|
|
||||||
``X-Requested-With`` header and set it to `XMLHttpRequest`.
|
|
||||||
Libraries that do that are prototype, jQuery and Mochikit and
|
|
||||||
probably some more.
|
|
||||||
|
|
||||||
.. class:: request
|
|
||||||
|
|
||||||
To access incoming request data, you can use the global `request`
|
To access incoming request data, you can use the global `request`
|
||||||
object. Flask parses incoming request data for you and gives you
|
object. Flask parses incoming request data for you and gives you
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Flask started in part to demonstrate how to build your own framework on top of
|
||||||
existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it
|
existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it
|
||||||
developed, it became useful to a wide audience. As you grow your codebase,
|
developed, it became useful to a wide audience. As you grow your codebase,
|
||||||
don't just use Flask -- understand it. Read the source. Flask's code is
|
don't just use Flask -- understand it. Read the source. Flask's code is
|
||||||
written to be read; it's documentation is published so you can use its internal
|
written to be read; its documentation is published so you can use its internal
|
||||||
APIs. Flask sticks to documented APIs in upstream libraries, and documents its
|
APIs. Flask sticks to documented APIs in upstream libraries, and documents its
|
||||||
internal utilities so that you can find the hook points needed for your
|
internal utilities so that you can find the hook points needed for your
|
||||||
project.
|
project.
|
||||||
|
|
|
||||||
|
|
@ -177,11 +177,11 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
|
||||||
admin = Blueprint('admin', __name__, template_folder='templates')
|
admin = Blueprint('admin', __name__, template_folder='templates')
|
||||||
|
|
||||||
For static files, the path can be absolute or relative to the blueprint
|
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
|
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
|
priority than the actual application's template folder. That way you can
|
||||||
easily override templates that a blueprint provides in the actual application.
|
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
|
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
|
overridden, make sure that no other blueprint or actual application template
|
||||||
has the same relative path. When multiple blueprints provide the same relative
|
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
|
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
|
||||||
for the extra ``admin`` folder is to avoid getting our template overridden
|
for the extra ``admin`` folder is to avoid getting our template overridden
|
||||||
by a template named ``index.html`` in the actual application template
|
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
|
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
|
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):
|
def page_not_found(e):
|
||||||
return render_template('pages/404.html')
|
return render_template('pages/404.html')
|
||||||
|
|
||||||
|
Most errorhandlers will simply work as expected; however, there is a caveat
|
||||||
|
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
|
||||||
|
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
|
||||||
|
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
|
||||||
|
access. This is because the blueprint does not "own" a certain URL space, so
|
||||||
|
the application instance has no way of knowing which blueprint errorhandler it
|
||||||
|
should run if given an invalid URL. If you would like to execute different
|
||||||
|
handling strategies for these errors based on URL prefixes, they may be defined
|
||||||
|
at the application level using the ``request`` proxy object::
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def _handle_api_error(ex):
|
||||||
|
if request.path.startswith('/api/'):
|
||||||
|
return jsonify_error(ex)
|
||||||
|
else:
|
||||||
|
return ex
|
||||||
|
|
||||||
More information on error handling see :ref:`errorpages`.
|
More information on error handling see :ref:`errorpages`.
|
||||||
|
|
|
||||||
16
docs/cli.rst
16
docs/cli.rst
|
|
@ -56,6 +56,18 @@ If you are constantly working with a virtualenv you can also put the
|
||||||
bottom of the file. That way every time you activate your virtualenv you
|
bottom of the file. That way every time you activate your virtualenv you
|
||||||
automatically also activate the correct application name.
|
automatically also activate the correct application name.
|
||||||
|
|
||||||
|
Edit the activate script for the shell you use. For example:
|
||||||
|
|
||||||
|
Unix Bash: ``venv/bin/activate``::
|
||||||
|
|
||||||
|
FLASK_APP=hello
|
||||||
|
export FLASK_APP
|
||||||
|
|
||||||
|
Windows CMD.exe: ``venv\Scripts\activate.bat``::
|
||||||
|
|
||||||
|
set "FLASK_APP=hello"
|
||||||
|
:END
|
||||||
|
|
||||||
Debug Flag
|
Debug Flag
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
@ -139,8 +151,8 @@ This could be a file named :file:`autoapp.py` with these contents::
|
||||||
from yourapplication import create_app
|
from yourapplication import create_app
|
||||||
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
app = create_app(os.environ['YOURAPPLICATION_CONFIG'])
|
||||||
|
|
||||||
Once this has happened you can make the flask command automatically pick
|
Once this has happened you can make the :command:`flask` command automatically
|
||||||
it up::
|
pick it up::
|
||||||
|
|
||||||
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
export YOURAPPLICATION_CONFIG=/path/to/config.cfg
|
||||||
export FLASK_APP=/path/to/autoapp.py
|
export FLASK_APP=/path/to/autoapp.py
|
||||||
|
|
|
||||||
19
docs/conf.py
19
docs/conf.py
|
|
@ -11,10 +11,13 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from datetime import datetime
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
|
@ -35,6 +38,14 @@ extensions = [
|
||||||
'flaskdocext'
|
'flaskdocext'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
__import__('sphinxcontrib.log_cabinet')
|
||||||
|
except ImportError:
|
||||||
|
print('sphinxcontrib-log-cabinet is not installed.')
|
||||||
|
print('Changelog directives will not be re-organized.')
|
||||||
|
else:
|
||||||
|
extensions.append('sphinxcontrib.log_cabinet')
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
|
@ -49,7 +60,7 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Flask'
|
project = u'Flask'
|
||||||
copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().year)
|
copyright = u'2010 - {0}, Armin Ronacher'.format(BUILD_DATE.year)
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
|
@ -231,7 +242,7 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
|
||||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||||
#epub_scheme = ''
|
#epub_scheme = ''
|
||||||
|
|
||||||
# The unique identifier of the text. This can be a ISBN number
|
# The unique identifier of the text. This can be an ISBN number
|
||||||
# or the project homepage.
|
# or the project homepage.
|
||||||
#epub_identifier = ''
|
#epub_identifier = ''
|
||||||
|
|
||||||
|
|
@ -257,7 +268,7 @@ intersphinx_mapping = {
|
||||||
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
|
||||||
'click': ('http://click.pocoo.org/', None),
|
'click': ('http://click.pocoo.org/', None),
|
||||||
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
'jinja': ('http://jinja.pocoo.org/docs/', None),
|
||||||
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
|
'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None),
|
||||||
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
|
||||||
'blinker': ('https://pythonhosted.org/blinker/', None)
|
'blinker': ('https://pythonhosted.org/blinker/', None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,21 @@ method::
|
||||||
SECRET_KEY='...'
|
SECRET_KEY='...'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.. admonition:: Debug Mode with the ``flask`` Script
|
||||||
|
|
||||||
|
If you use the :command:`flask` script to start a local development
|
||||||
|
server, to enable the debug mode, you need to export the ``FLASK_DEBUG``
|
||||||
|
environment variable before running the server::
|
||||||
|
|
||||||
|
$ export FLASK_DEBUG=1
|
||||||
|
$ flask run
|
||||||
|
|
||||||
|
(On Windows you need to use ``set`` instead of ``export``).
|
||||||
|
|
||||||
|
``app.debug`` and ``app.config['DEBUG']`` are not compatible with
|
||||||
|
the :command:`flask` script. They only worked when using ``Flask.run()``
|
||||||
|
method.
|
||||||
|
|
||||||
Builtin Configuration Values
|
Builtin Configuration Values
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
|
@ -52,7 +67,8 @@ The following configuration values are used internally by Flask:
|
||||||
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
|
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
|
||||||
|
|
||||||
================================= =========================================
|
================================= =========================================
|
||||||
``DEBUG`` enable/disable debug mode
|
``DEBUG`` enable/disable debug mode when using
|
||||||
|
``Flask.run()`` method to start server
|
||||||
``TESTING`` enable/disable testing mode
|
``TESTING`` enable/disable testing mode
|
||||||
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
|
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
|
||||||
propagation of exceptions. If not set or
|
propagation of exceptions. If not set or
|
||||||
|
|
@ -116,13 +132,13 @@ The following configuration values are used internally by Flask:
|
||||||
by default enables URL generation
|
by default enables URL generation
|
||||||
without a request context but with an
|
without a request context but with an
|
||||||
application context.
|
application context.
|
||||||
``APPLICATION_ROOT`` If the application does not occupy
|
``APPLICATION_ROOT`` The path value used for the session
|
||||||
a whole domain or subdomain this can
|
cookie if ``SESSION_COOKIE_PATH`` isn't
|
||||||
be set to the path where the application
|
set. If it's also ``None`` ``'/'`` is used.
|
||||||
is configured to live. This is for
|
Note that to actually serve your Flask
|
||||||
session cookie as path value. If
|
app under a subpath you need to tell
|
||||||
domains are used, this should be
|
your WSGI container the ``SCRIPT_NAME``
|
||||||
``None``.
|
WSGI environment variable.
|
||||||
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||||
reject incoming requests with a
|
reject incoming requests with a
|
||||||
content length greater than this by
|
content length greater than this by
|
||||||
|
|
@ -178,11 +194,9 @@ The following configuration values are used internally by Flask:
|
||||||
This is not recommended but might give
|
This is not recommended but might give
|
||||||
you a performance improvement on the
|
you a performance improvement on the
|
||||||
cost of cacheability.
|
cost of cacheability.
|
||||||
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default)
|
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` or the Flask app
|
||||||
jsonify responses will be pretty printed
|
is running in debug mode, jsonify responses
|
||||||
if they are not requested by an
|
will be pretty printed.
|
||||||
XMLHttpRequest object (controlled by
|
|
||||||
the ``X-Requested-With`` header)
|
|
||||||
``JSONIFY_MIMETYPE`` MIME type used for jsonify responses.
|
``JSONIFY_MIMETYPE`` MIME type used for jsonify responses.
|
||||||
``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of
|
``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of
|
||||||
the template source and reload it
|
the template source and reload it
|
||||||
|
|
@ -262,7 +276,7 @@ So a common pattern is this::
|
||||||
|
|
||||||
This first loads the configuration from the
|
This first loads the configuration from the
|
||||||
`yourapplication.default_settings` module and then overrides the values
|
`yourapplication.default_settings` module and then overrides the values
|
||||||
with the contents of the file the :envvar:``YOURAPPLICATION_SETTINGS``
|
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||||
environment variable points to. This environment variable can be set on
|
environment variable points to. This environment variable can be set on
|
||||||
Linux or OS X with the export command in the shell before starting the
|
Linux or OS X with the export command in the shell before starting the
|
||||||
server::
|
server::
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ A basic FastCGI configuration for lighttpd looks like that::
|
||||||
)
|
)
|
||||||
|
|
||||||
alias.url = (
|
alias.url = (
|
||||||
"/static/" => "/path/to/your/static"
|
"/static/" => "/path/to/your/static/"
|
||||||
)
|
)
|
||||||
|
|
||||||
url.rewrite-once = (
|
url.rewrite-once = (
|
||||||
|
|
@ -159,7 +159,7 @@ work in the URL root you have to work around a lighttpd bug with the
|
||||||
|
|
||||||
Make sure to apply it only if you are mounting the application the URL
|
Make sure to apply it only if you are mounting the application the URL
|
||||||
root. Also, see the Lighty docs for more information on `FastCGI and Python
|
root. Also, see the Lighty docs for more information on `FastCGI and Python
|
||||||
<http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>`_ (note that
|
<https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI>`_ (note that
|
||||||
explicitly passing a socket to run() is no longer necessary).
|
explicitly passing a socket to run() is no longer necessary).
|
||||||
|
|
||||||
Configuring nginx
|
Configuring nginx
|
||||||
|
|
@ -234,7 +234,7 @@ python path. Common problems are:
|
||||||
web server.
|
web server.
|
||||||
- Different python interpreters being used.
|
- Different python interpreters being used.
|
||||||
|
|
||||||
.. _nginx: http://nginx.org/
|
.. _nginx: https://nginx.org/
|
||||||
.. _lighttpd: http://www.lighttpd.net/
|
.. _lighttpd: https://www.lighttpd.net/
|
||||||
.. _cherokee: http://cherokee-project.com/
|
.. _cherokee: http://cherokee-project.com/
|
||||||
.. _flup: https://pypi.python.org/pypi/flup
|
.. _flup: https://pypi.python.org/pypi/flup
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Hosted options
|
||||||
- `Deploying Flask on OpenShift <https://developers.openshift.com/en/python-flask.html>`_
|
- `Deploying Flask on OpenShift <https://developers.openshift.com/en/python-flask.html>`_
|
||||||
- `Deploying Flask on Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
- `Deploying Flask on Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
||||||
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
||||||
|
- `Deploying Flask on AWS Elastic Beanstalk <https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-flask.html>`_
|
||||||
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
||||||
- `Deploying on Azure (IIS) <https://azure.microsoft.com/documentation/articles/web-sites-python-configure/>`_
|
- `Deploying on Azure (IIS) <https://azure.microsoft.com/documentation/articles/web-sites-python-configure/>`_
|
||||||
|
- `Deploying on PythonAnywhere <https://help.pythonanywhere.com/pages/Flask/>`_
|
||||||
|
|
||||||
Self-hosted options
|
Self-hosted options
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ If you are using the `Apache`_ webserver, consider using `mod_wsgi`_.
|
||||||
not called because this will always start a local WSGI server which
|
not called because this will always start a local WSGI server which
|
||||||
we do not want if we deploy that application to mod_wsgi.
|
we do not want if we deploy that application to mod_wsgi.
|
||||||
|
|
||||||
.. _Apache: http://httpd.apache.org/
|
.. _Apache: https://httpd.apache.org/
|
||||||
|
|
||||||
Installing `mod_wsgi`
|
Installing `mod_wsgi`
|
||||||
---------------------
|
---------------------
|
||||||
|
|
@ -114,7 +114,7 @@ refuse to run with the above configuration. On a Windows system, eliminate those
|
||||||
|
|
||||||
Note: There have been some changes in access control configuration for `Apache 2.4`_.
|
Note: There have been some changes in access control configuration for `Apache 2.4`_.
|
||||||
|
|
||||||
.. _Apache 2.4: http://httpd.apache.org/docs/trunk/upgrading.html
|
.. _Apache 2.4: https://httpd.apache.org/docs/trunk/upgrading.html
|
||||||
|
|
||||||
Most notably, the syntax for directory permissions has changed from httpd 2.2
|
Most notably, the syntax for directory permissions has changed from httpd 2.2
|
||||||
|
|
||||||
|
|
@ -133,9 +133,9 @@ to httpd 2.4 syntax
|
||||||
For more information consult the `mod_wsgi documentation`_.
|
For more information consult the `mod_wsgi documentation`_.
|
||||||
|
|
||||||
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
||||||
.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html
|
.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html
|
||||||
.. _virtual python: https://pypi.python.org/pypi/virtualenv
|
.. _virtual python: https://pypi.python.org/pypi/virtualenv
|
||||||
.. _mod_wsgi documentation: http://modwsgi.readthedocs.io/en/develop/index.html
|
.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ to have it in the URL root its a bit simpler::
|
||||||
uwsgi_pass unix:/tmp/yourapplication.sock;
|
uwsgi_pass unix:/tmp/yourapplication.sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
.. _nginx: http://nginx.org/
|
.. _nginx: https://nginx.org/
|
||||||
.. _lighttpd: http://www.lighttpd.net/
|
.. _lighttpd: https://www.lighttpd.net/
|
||||||
.. _cherokee: http://cherokee-project.com/
|
.. _cherokee: http://cherokee-project.com/
|
||||||
.. _uwsgi: http://projects.unbit.it/uwsgi/
|
.. _uwsgi: http://projects.unbit.it/uwsgi/
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ Error Logging Tools
|
||||||
Sending error mails, even if just for critical ones, can become
|
Sending error mails, even if just for critical ones, can become
|
||||||
overwhelming if enough users are hitting the error and log files are
|
overwhelming if enough users are hitting the error and log files are
|
||||||
typically never looked at. This is why we recommend using `Sentry
|
typically never looked at. This is why we recommend using `Sentry
|
||||||
<http://www.getsentry.com/>`_ for dealing with application errors. It's
|
<https://www.getsentry.com/>`_ for dealing with application errors. It's
|
||||||
available as an Open Source project `on GitHub
|
available as an Open Source project `on GitHub
|
||||||
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
<https://github.com/getsentry/sentry>`__ and is also available as a `hosted version
|
||||||
<https://getsentry.com/signup/>`_ which you can try for free. Sentry
|
<https://getsentry.com/signup/>`_ which you can try for free. Sentry
|
||||||
|
|
@ -89,7 +89,7 @@ Register error handlers using :meth:`~flask.Flask.errorhandler` or
|
||||||
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
@app.errorhandler(werkzeug.exceptions.BadRequest)
|
||||||
def handle_bad_request(e):
|
def handle_bad_request(e):
|
||||||
return 'bad request!'
|
return 'bad request!'
|
||||||
|
|
||||||
app.register_error_handler(400, lambda e: 'bad request!')
|
app.register_error_handler(400, lambda e: 'bad request!')
|
||||||
|
|
||||||
Those two ways are equivalent, but the first one is more clear and leaves
|
Those two ways are equivalent, but the first one is more clear and leaves
|
||||||
|
|
@ -216,7 +216,7 @@ A formatter can be instantiated with a format string. Note that
|
||||||
tracebacks are appended to the log entry automatically. You don't have to
|
tracebacks are appended to the log entry automatically. You don't have to
|
||||||
do that in the log formatter format string.
|
do that in the log formatter format string.
|
||||||
|
|
||||||
Here some example setups:
|
Here are some example setups:
|
||||||
|
|
||||||
Email
|
Email
|
||||||
`````
|
`````
|
||||||
|
|
@ -276,8 +276,9 @@ that this list is not complete, consult the official documentation of the
|
||||||
| ``%(lineno)d`` | Source line number where the logging call was |
|
| ``%(lineno)d`` | Source line number where the logging call was |
|
||||||
| | issued (if available). |
|
| | issued (if available). |
|
||||||
+------------------+----------------------------------------------------+
|
+------------------+----------------------------------------------------+
|
||||||
| ``%(asctime)s`` | Human-readable time when the LogRecord` was |
|
| ``%(asctime)s`` | Human-readable time when the |
|
||||||
| | created. By default this is of the form |
|
| | :class:`~logging.LogRecord` was created. |
|
||||||
|
| | By default this is of the form |
|
||||||
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
|
||||||
| | the comma are millisecond portion of the time). |
|
| | the comma are millisecond portion of the time). |
|
||||||
| | This can be changed by subclassing the formatter |
|
| | This can be changed by subclassing the formatter |
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,6 @@ be something like "Flask-SimpleXML". Make sure to include the name
|
||||||
This is how users can then register dependencies to your extension in
|
This is how users can then register dependencies to your extension in
|
||||||
their :file:`setup.py` files.
|
their :file:`setup.py` files.
|
||||||
|
|
||||||
Flask sets up a redirect package called :data:`flask.ext` where users
|
|
||||||
should import the extensions from. If you for instance have a package
|
|
||||||
called ``flask_something`` users would import it as
|
|
||||||
``flask.ext.something``. This is done to transition from the old
|
|
||||||
namespace packages. See :ref:`ext-import-transition` for more details.
|
|
||||||
|
|
||||||
But what do extensions look like themselves? An extension has to ensure
|
But what do extensions look like themselves? An extension has to ensure
|
||||||
that it works with multiple Flask application instances at once. This is
|
that it works with multiple Flask application instances at once. This is
|
||||||
a requirement because many people will use patterns like the
|
a requirement because many people will use patterns like the
|
||||||
|
|
@ -393,8 +387,6 @@ extension to be approved you have to follow these guidelines:
|
||||||
Python 2.7
|
Python 2.7
|
||||||
|
|
||||||
|
|
||||||
.. _ext-import-transition:
|
|
||||||
|
|
||||||
Extension Import Transition
|
Extension Import Transition
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|
@ -413,6 +405,6 @@ schema. The ``flask.ext.foo`` compatibility alias is still in Flask 0.11 but is
|
||||||
now deprecated -- you should use ``flask_foo``.
|
now deprecated -- you should use ``flask_foo``.
|
||||||
|
|
||||||
|
|
||||||
.. _OAuth extension: http://pythonhosted.org/Flask-OAuth/
|
.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/
|
||||||
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
||||||
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
||||||
|
|
|
||||||
|
|
@ -3,163 +3,173 @@
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
Flask depends on some external libraries, like `Werkzeug
|
Python Version
|
||||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_.
|
--------------
|
||||||
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
|
||||||
applications and a variety of servers for both development and deployment.
|
|
||||||
Jinja2 renders templates.
|
|
||||||
|
|
||||||
So how do you get all that on your computer quickly? There are many ways you
|
We recommend using the latest version of Python 3. Flask supports Python 3.3
|
||||||
could do that, but the most kick-ass method is virtualenv, so let's have a look
|
and newer, Python 2.6 and newer, and PyPy.
|
||||||
at that first.
|
|
||||||
|
|
||||||
You will need Python 2.6 or newer to get started, so be sure to have an
|
Dependencies
|
||||||
up-to-date Python 2.x installation. For using Flask with Python 3 have a
|
------------
|
||||||
look at :ref:`python3-support`.
|
|
||||||
|
|
||||||
.. _virtualenv:
|
These distributions will be installed automatically when installing Flask.
|
||||||
|
|
||||||
virtualenv
|
* `Werkzeug`_ implements WSGI, the standard Python interface between
|
||||||
----------
|
applications and servers.
|
||||||
|
* `Jinja`_ is a template language that renders the pages your application
|
||||||
|
serves.
|
||||||
|
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
|
||||||
|
templates to avoid injection attacks.
|
||||||
|
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
|
||||||
|
to protect Flask's session cookie.
|
||||||
|
* `Click`_ is a framework for writing command line applications. It provides
|
||||||
|
the ``flask`` command and allows adding custom management commands.
|
||||||
|
|
||||||
Virtualenv is probably what you want to use during development, and if you have
|
.. _Werkzeug: http://werkzeug.pocoo.org/
|
||||||
shell access to your production machines, you'll probably want to use it there,
|
.. _Jinja: http://jinja.pocoo.org/
|
||||||
too.
|
.. _MarkupSafe: https://pypi.python.org/pypi/MarkupSafe
|
||||||
|
.. _ItsDangerous: https://pythonhosted.org/itsdangerous/
|
||||||
|
.. _Click: http://click.pocoo.org/
|
||||||
|
|
||||||
What problem does virtualenv solve? If you like Python as much as I do,
|
Optional dependencies
|
||||||
chances are you want to use it for other projects besides Flask-based web
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
applications. But the more projects you have, the more likely it is that you
|
|
||||||
will be working with different versions of Python itself, or at least different
|
|
||||||
versions of Python libraries. Let's face it: quite often libraries break
|
|
||||||
backwards compatibility, and it's unlikely that any serious application will
|
|
||||||
have zero dependencies. So what do you do if two or more of your projects have
|
|
||||||
conflicting dependencies?
|
|
||||||
|
|
||||||
Virtualenv to the rescue! Virtualenv enables multiple side-by-side
|
These distributions will not be installed automatically. Flask will detect and
|
||||||
installations of Python, one for each project. It doesn't actually install
|
use them if you install them.
|
||||||
separate copies of Python, but it does provide a clever way to keep different
|
|
||||||
project environments isolated. Let's see how virtualenv works.
|
|
||||||
|
|
||||||
If you are on Mac OS X or Linux, chances are that the following
|
* `Blinker`_ provides support for :ref:`signals`.
|
||||||
command will work for you::
|
* `SimpleJSON`_ is a fast JSON implementation that is compatible with
|
||||||
|
Python's ``json`` module. It is preferred for JSON operations if it is
|
||||||
|
installed.
|
||||||
|
|
||||||
$ sudo pip install virtualenv
|
.. _Blinker: https://pythonhosted.org/blinker/
|
||||||
|
.. _SimpleJSON: https://simplejson.readthedocs.io/
|
||||||
|
|
||||||
It will probably install virtualenv on your system. Maybe it's even
|
Virtual environments
|
||||||
in your package manager. If you use Ubuntu, try::
|
--------------------
|
||||||
|
|
||||||
$ sudo apt-get install python-virtualenv
|
Use a virtual environment to manage the dependencies for your project, both in
|
||||||
|
development and in production.
|
||||||
|
|
||||||
If you are on Windows and don't have the ``easy_install`` command, you must
|
What problem does a virtual environment solve? The more Python projects you
|
||||||
install it first. Check the :ref:`windows-easy-install` section for more
|
have, the more likely it is that you need to work with different versions of
|
||||||
information about how to do that. Once you have it installed, run the same
|
Python libraries, or even Python itself. Newer versions of libraries for one
|
||||||
commands as above, but without the ``sudo`` prefix.
|
project can break compatibility in another project.
|
||||||
|
|
||||||
Once you have virtualenv installed, just fire up a shell and create
|
Virtual environments are independent groups of Python libraries, one for each
|
||||||
your own environment. I usually create a project folder and a :file:`venv`
|
project. Packages installed for one project will not affect other projects or
|
||||||
folder within::
|
the operating system's packages.
|
||||||
|
|
||||||
$ mkdir myproject
|
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
||||||
$ cd myproject
|
environments. If you're using a modern version of Python, you can continue on
|
||||||
$ virtualenv venv
|
to the next section.
|
||||||
New python executable in venv/bin/python
|
|
||||||
Installing setuptools, pip............done.
|
|
||||||
|
|
||||||
Now, whenever you want to work on a project, you only have to activate the
|
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
||||||
corresponding environment. On OS X and Linux, do the following::
|
|
||||||
|
|
||||||
$ . venv/bin/activate
|
.. _install-create-env:
|
||||||
|
|
||||||
If you are a Windows user, the following command is for you::
|
Create an environment
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
$ venv\scripts\activate
|
Create a project folder and a :file:`venv` folder within:
|
||||||
|
|
||||||
Either way, you should now be using your virtualenv (notice how the prompt of
|
.. code-block:: sh
|
||||||
your shell has changed to show the active environment).
|
|
||||||
|
|
||||||
And if you want to go back to the real world, use the following command::
|
mkdir myproject
|
||||||
|
cd myproject
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
$ deactivate
|
On Windows:
|
||||||
|
|
||||||
After doing this, the prompt of your shell should be as familiar as before.
|
.. code-block:: bat
|
||||||
|
|
||||||
Now, let's move on. Enter the following command to get Flask activated in your
|
py -3 -m venv venv
|
||||||
virtualenv::
|
|
||||||
|
|
||||||
$ pip install Flask
|
If you needed to install virtualenv because you are on an older version of
|
||||||
|
Python, use the following command instead:
|
||||||
|
|
||||||
A few seconds later and you are good to go.
|
.. code-block:: sh
|
||||||
|
|
||||||
|
virtualenv venv
|
||||||
|
|
||||||
System-Wide Installation
|
On Windows:
|
||||||
------------------------
|
|
||||||
|
|
||||||
This is possible as well, though I do not recommend it. Just run
|
.. code-block:: bat
|
||||||
``pip`` with root privileges::
|
|
||||||
|
|
||||||
$ sudo pip install Flask
|
\Python27\Scripts\virtualenv.exe venv
|
||||||
|
|
||||||
(On Windows systems, run it in a command-prompt window with administrator
|
Activate the environment
|
||||||
privileges, and leave out ``sudo``.)
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Before you work on your project, activate the corresponding environment:
|
||||||
|
|
||||||
Living on the Edge
|
.. code-block:: sh
|
||||||
|
|
||||||
|
. venv/bin/activate
|
||||||
|
|
||||||
|
On Windows:
|
||||||
|
|
||||||
|
.. code-block:: bat
|
||||||
|
|
||||||
|
venv\Scripts\activate
|
||||||
|
|
||||||
|
Your shell prompt will change to show the name of the activated environment.
|
||||||
|
|
||||||
|
Install Flask
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Within the activated environment, use the following command to install Flask:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
pip install Flask
|
||||||
|
|
||||||
|
Living on the edge
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you want to work with the latest Flask code before it's released, install or
|
||||||
|
update the code from the master branch:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
pip install -U https://github.com/pallets/flask/archive/master.tar.gz
|
||||||
|
|
||||||
|
.. _install-install-virtualenv:
|
||||||
|
|
||||||
|
Install virtualenv
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If you want to work with the latest version of Flask, there are two ways: you
|
If you are using Python 2, the venv module is not available. Instead,
|
||||||
can either let ``pip`` pull in the development version, or you can tell
|
install `virtualenv`_.
|
||||||
it to operate on a git checkout. Either way, virtualenv is recommended.
|
|
||||||
|
|
||||||
Get the git checkout in a new virtualenv and run in development mode::
|
On Linux, virtualenv is provided by your package manager:
|
||||||
|
|
||||||
$ git clone http://github.com/pallets/flask.git
|
.. code-block:: sh
|
||||||
Initialized empty Git repository in ~/dev/flask/.git/
|
|
||||||
$ cd flask
|
|
||||||
$ virtualenv venv
|
|
||||||
New python executable in venv/bin/python
|
|
||||||
Installing setuptools, pip............done.
|
|
||||||
$ . venv/bin/activate
|
|
||||||
$ python setup.py develop
|
|
||||||
...
|
|
||||||
Finished processing dependencies for Flask
|
|
||||||
|
|
||||||
This will pull in the dependencies and activate the git head as the current
|
# Debian, Ubuntu
|
||||||
version inside the virtualenv. Then all you have to do is run ``git pull
|
sudo apt-get install python-virtualenv
|
||||||
origin`` to update to the latest version.
|
|
||||||
|
|
||||||
.. _windows-easy-install:
|
# CentOS, Fedora
|
||||||
|
sudo yum install python-virtualenv
|
||||||
|
|
||||||
`pip` and `setuptools` on Windows
|
# Arch
|
||||||
---------------------------------
|
sudo pacman -S python-virtualenv
|
||||||
|
|
||||||
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools``
|
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
|
||||||
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial
|
|
||||||
package you will need is pip - this will let you install
|
|
||||||
anything else (like virtualenv). Fortunately there is a "bootstrap script"
|
|
||||||
you can run to install.
|
|
||||||
|
|
||||||
If you don't currently have ``pip``, then `get-pip.py` will install it for you.
|
.. code-block:: sh
|
||||||
|
|
||||||
`get-pip.py`_
|
sudo python2 Downloads/get-pip.py
|
||||||
|
sudo python2 -m pip install virtualenv
|
||||||
|
|
||||||
It should be double-clickable once you download it. If you already have ``pip``,
|
On Windows, as an administrator:
|
||||||
you can upgrade them by running::
|
|
||||||
|
|
||||||
> pip install --upgrade pip setuptools
|
.. code-block:: bat
|
||||||
|
|
||||||
Most often, once you pull up a command prompt you want to be able to type ``pip``
|
\Python27\python.exe Downloads\get-pip.py
|
||||||
and ``python`` which will run those things, but this might not automatically happen
|
\Python27\python.exe -m pip install virtualenv
|
||||||
on Windows, because it doesn't know where those executables are (give either a try!).
|
|
||||||
|
|
||||||
To fix this, you should be able to navigate to your Python install directory
|
Now you can continue to :ref:`install-create-env`.
|
||||||
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
|
|
||||||
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
|
|
||||||
check that you can now just type ``python`` to bring up the interpreter.
|
|
||||||
|
|
||||||
Finally, to install `virtualenv`_, you can simply run::
|
|
||||||
|
|
||||||
> pip install virtualenv
|
|
||||||
|
|
||||||
Then you can be off on your way following the installation instructions above.
|
|
||||||
|
|
||||||
|
.. _virtualenv: https://virtualenv.pypa.io/
|
||||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Application Factories
|
||||||
If you are already using packages and blueprints for your application
|
If you are already using packages and blueprints for your application
|
||||||
(:ref:`blueprints`) there are a couple of really nice ways to further improve
|
(:ref:`blueprints`) there are a couple of really nice ways to further improve
|
||||||
the experience. A common pattern is creating the application object when
|
the experience. A common pattern is creating the application object when
|
||||||
the blueprint is imported. But if you move the creation of this object,
|
the blueprint is imported. But if you move the creation of this object
|
||||||
into a function, you can then create multiple instances of this app later.
|
into a function, you can then create multiple instances of this app later.
|
||||||
|
|
||||||
So why would you want to do this?
|
So why would you want to do this?
|
||||||
|
|
@ -60,7 +60,7 @@ Factories & Extensions
|
||||||
It's preferable to create your extensions and app factories so that the
|
It's preferable to create your extensions and app factories so that the
|
||||||
extension object does not initially get bound to the application.
|
extension object does not initially get bound to the application.
|
||||||
|
|
||||||
Using `Flask-SQLAlchemy <http://pythonhosted.org/Flask-SQLAlchemy/>`_,
|
Using `Flask-SQLAlchemy <http://flask-sqlalchemy.pocoo.org/>`_,
|
||||||
as an example, you should not do something along those lines::
|
as an example, you should not do something along those lines::
|
||||||
|
|
||||||
def create_app(config_filename):
|
def create_app(config_filename):
|
||||||
|
|
|
||||||
|
|
@ -3,71 +3,43 @@
|
||||||
Deferred Request Callbacks
|
Deferred Request Callbacks
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
One of the design principles of Flask is that response objects are created
|
One of the design principles of Flask is that response objects are created and
|
||||||
and passed down a chain of potential callbacks that can modify them or
|
passed down a chain of potential callbacks that can modify them or replace
|
||||||
replace them. When the request handling starts, there is no response
|
them. When the request handling starts, there is no response object yet. It is
|
||||||
object yet. It is created as necessary either by a view function or by
|
created as necessary either by a view function or by some other component in
|
||||||
some other component in the system.
|
the system.
|
||||||
|
|
||||||
But what happens if you want to modify the response at a point where the
|
What happens if you want to modify the response at a point where the response
|
||||||
response does not exist yet? A common example for that would be a
|
does not exist yet? A common example for that would be a
|
||||||
before-request function that wants to set a cookie on the response object.
|
:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
|
||||||
|
response object.
|
||||||
|
|
||||||
One way is to avoid the situation. Very often that is possible. For
|
One way is to avoid the situation. Very often that is possible. For instance
|
||||||
instance you can try to move that logic into an after-request callback
|
you can try to move that logic into a :meth:`~flask.Flask.after_request`
|
||||||
instead. Sometimes however moving that code there is just not a very
|
callback instead. However, sometimes moving code there makes it more
|
||||||
pleasant experience or makes code look very awkward.
|
more complicated or awkward to reason about.
|
||||||
|
|
||||||
As an alternative possibility you can attach a bunch of callback functions
|
As an alternative, you can use :func:`~flask.after_this_request` to register
|
||||||
to the :data:`~flask.g` object and call them at the end of the request.
|
callbacks that will execute after only the current request. This way you can
|
||||||
This way you can defer code execution from anywhere in the application.
|
defer code execution from anywhere in the application, based on the current
|
||||||
|
request.
|
||||||
|
|
||||||
The Decorator
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The following decorator is the key. It registers a function on a list on
|
|
||||||
the :data:`~flask.g` object::
|
|
||||||
|
|
||||||
from flask import g
|
|
||||||
|
|
||||||
def after_this_request(f):
|
|
||||||
if not hasattr(g, 'after_request_callbacks'):
|
|
||||||
g.after_request_callbacks = []
|
|
||||||
g.after_request_callbacks.append(f)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
Calling the Deferred
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Now you can use the `after_this_request` decorator to mark a function to
|
|
||||||
be called at the end of the request. But we still need to call them. For
|
|
||||||
this the following function needs to be registered as
|
|
||||||
:meth:`~flask.Flask.after_request` callback::
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def call_after_request_callbacks(response):
|
|
||||||
for callback in getattr(g, 'after_request_callbacks', ()):
|
|
||||||
callback(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
A Practical Example
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
At any time during a request, we can register a function to be called at the
|
At any time during a request, we can register a function to be called at the
|
||||||
end of the request. For example you can remember the current language of the
|
end of the request. For example you can remember the current language of the
|
||||||
user in a cookie in the before-request function::
|
user in a cookie in a :meth:`~flask.Flask.before_request` callback::
|
||||||
|
|
||||||
from flask import request
|
from flask import request, after_this_request
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def detect_user_language():
|
def detect_user_language():
|
||||||
language = request.cookies.get('user_lang')
|
language = request.cookies.get('user_lang')
|
||||||
|
|
||||||
if language is None:
|
if language is None:
|
||||||
language = guess_language_from_request()
|
language = guess_language_from_request()
|
||||||
|
|
||||||
|
# when the response exists, set a cookie with the language
|
||||||
@after_this_request
|
@after_this_request
|
||||||
def remember_language(response):
|
def remember_language(response):
|
||||||
response.set_cookie('user_lang', language)
|
response.set_cookie('user_lang', language)
|
||||||
|
|
||||||
g.language = language
|
g.language = language
|
||||||
|
|
|
||||||
|
|
@ -174,4 +174,4 @@ the code without having to run ``install`` again after each change.
|
||||||
|
|
||||||
|
|
||||||
.. _pip: https://pypi.python.org/pypi/pip
|
.. _pip: https://pypi.python.org/pypi/pip
|
||||||
.. _Setuptools: https://pythonhosted.org/setuptools
|
.. _Setuptools: https://pypi.python.org/pypi/setuptools
|
||||||
|
|
|
||||||
|
|
@ -47,37 +47,53 @@ even if the application behaves correctly:
|
||||||
Error Handlers
|
Error Handlers
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
An error handler is a function, just like a view function, but it is
|
An error handler is a function that returns a response when a type of error is
|
||||||
called when an error happens and is passed that error. The error is most
|
raised, similar to how a view is a function that returns a response when a
|
||||||
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
request URL is matched. It is passed the instance of the error being handled,
|
||||||
can be a different error: a handler for internal server errors will be
|
which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error
|
||||||
passed other exception instances as well if they are uncaught.
|
handler for "500 Internal Server Error" will be passed uncaught exceptions in
|
||||||
|
addition to explicit 500 errors.
|
||||||
|
|
||||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||||
decorator and the error code of the exception. Keep in mind that Flask
|
decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler
|
||||||
will *not* set the error code for you, so make sure to also provide the
|
can be registered for a status code, like 404, or for an exception class.
|
||||||
HTTP status code when returning a response.
|
|
||||||
|
|
||||||
Please note that if you add an error handler for "500 Internal Server
|
The status code of the response will not be set to the handler's code. Make
|
||||||
Error", Flask will not trigger it if it's running in Debug mode.
|
sure to provide the appropriate HTTP status code when returning a response from
|
||||||
|
a handler.
|
||||||
|
|
||||||
Here an example implementation for a "404 Page Not Found" exception::
|
A handler for "500 Internal Server Error" will not be used when running in
|
||||||
|
debug mode. Instead, the interactive debugger will be shown.
|
||||||
|
|
||||||
|
Here is an example implementation for a "404 Page Not Found" exception::
|
||||||
|
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def page_not_found(e):
|
def page_not_found(e):
|
||||||
|
# note that we set the 404 status explicitly
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
When using the :ref:`application factory pattern <app-factories>`::
|
||||||
|
|
||||||
|
from flask import Flask, render_template
|
||||||
|
|
||||||
|
def page_not_found(e):
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
def create_app(config_filename):
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.register_error_handler(404, page_not_found)
|
||||||
|
return app
|
||||||
|
|
||||||
An example template might be this:
|
An example template might be this:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block title %}Page Not Found{% endblock %}
|
{% block title %}Page Not Found{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>Page Not Found</h1>
|
<h1>Page Not Found</h1>
|
||||||
<p>What you were looking for is just not there.
|
<p>What you were looking for is just not there.
|
||||||
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
<p><a href="{{ url_for('index') }}">go somewhere nice</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,5 +49,5 @@ web server's documentation.
|
||||||
See also
|
See also
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* The `Favicon <http://en.wikipedia.org/wiki/Favicon>`_ article on
|
* The `Favicon <https://en.wikipedia.org/wiki/Favicon>`_ article on
|
||||||
Wikipedia
|
Wikipedia
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ specific upload folder and displays a file to the user. Let's look at the
|
||||||
bootstrapping code for our application::
|
bootstrapping code for our application::
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from flask import Flask, request, redirect, url_for
|
from flask import Flask, flash, request, redirect, url_for
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||||
|
|
@ -58,7 +58,7 @@ the file and redirects the user to the URL for the uploaded file::
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
# if user does not select file, browser also
|
# if user does not select file, browser also
|
||||||
# submit a empty part without filename
|
# submit an empty part without filename
|
||||||
if file.filename == '':
|
if file.filename == '':
|
||||||
flash('No selected file')
|
flash('No selected file')
|
||||||
return redirect(request.url)
|
return redirect(request.url)
|
||||||
|
|
@ -72,8 +72,8 @@ the file and redirects the user to the URL for the uploaded file::
|
||||||
<title>Upload new File</title>
|
<title>Upload new File</title>
|
||||||
<h1>Upload new File</h1>
|
<h1>Upload new File</h1>
|
||||||
<form method=post enctype=multipart/form-data>
|
<form method=post enctype=multipart/form-data>
|
||||||
<p><input type=file name=file>
|
<input type=file name=file>
|
||||||
<input type=submit value=Upload>
|
<input type=submit value=Upload>
|
||||||
</form>
|
</form>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
@ -181,4 +181,4 @@ applications dealing with uploads, there is also a Flask extension called
|
||||||
blacklisting of extensions and more.
|
blacklisting of extensions and more.
|
||||||
|
|
||||||
.. _jQuery: https://jquery.com/
|
.. _jQuery: https://jquery.com/
|
||||||
.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/
|
.. _Flask-Uploads: https://pythonhosted.org/Flask-Uploads/
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ this::
|
||||||
login.html
|
login.html
|
||||||
...
|
...
|
||||||
|
|
||||||
|
If you find yourself stuck on something, feel free
|
||||||
|
to take a look at the source code for this example.
|
||||||
|
You'll find `the full src for this example here`_.
|
||||||
|
|
||||||
Simple Packages
|
Simple Packages
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
@ -61,7 +65,7 @@ that tells Flask where to find the application instance::
|
||||||
export FLASK_APP=yourapplication
|
export FLASK_APP=yourapplication
|
||||||
|
|
||||||
If you are outside of the project directory make sure to provide the exact
|
If you are outside of the project directory make sure to provide the exact
|
||||||
path to your application directory. Similiarly you can turn on "debug
|
path to your application directory. Similarly you can turn on "debug
|
||||||
mode" with this environment variable::
|
mode" with this environment variable::
|
||||||
|
|
||||||
export FLASK_DEBUG=true
|
export FLASK_DEBUG=true
|
||||||
|
|
@ -130,6 +134,7 @@ You should then end up with something like that::
|
||||||
|
|
||||||
|
|
||||||
.. _working-with-modules:
|
.. _working-with-modules:
|
||||||
|
.. _the full src for this example here: https://github.com/pallets/flask/tree/master/examples/patterns/largerapp
|
||||||
|
|
||||||
Working with Blueprints
|
Working with Blueprints
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ if you want to get started quickly.
|
||||||
You can download `Flask-SQLAlchemy`_ from `PyPI
|
You can download `Flask-SQLAlchemy`_ from `PyPI
|
||||||
<https://pypi.python.org/pypi/Flask-SQLAlchemy>`_.
|
<https://pypi.python.org/pypi/Flask-SQLAlchemy>`_.
|
||||||
|
|
||||||
.. _Flask-SQLAlchemy: http://pythonhosted.org/Flask-SQLAlchemy/
|
.. _Flask-SQLAlchemy: http://flask-sqlalchemy.pocoo.org/
|
||||||
|
|
||||||
|
|
||||||
Declarative
|
Declarative
|
||||||
|
|
@ -108,9 +108,9 @@ Querying is simple as well:
|
||||||
>>> User.query.filter(User.name == 'admin').first()
|
>>> User.query.filter(User.name == 'admin').first()
|
||||||
<User u'admin'>
|
<User u'admin'>
|
||||||
|
|
||||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
.. _declarative:
|
.. _declarative:
|
||||||
http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/
|
||||||
|
|
||||||
Manual Object Relational Mapping
|
Manual Object Relational Mapping
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
@ -135,7 +135,7 @@ Here is an example :file:`database.py` module for your application::
|
||||||
def init_db():
|
def init_db():
|
||||||
metadata.create_all(bind=engine)
|
metadata.create_all(bind=engine)
|
||||||
|
|
||||||
As for the declarative approach you need to close the session after
|
As in the declarative approach, you need to close the session after
|
||||||
each request or application context shutdown. Put this into your
|
each request or application context shutdown. Put this into your
|
||||||
application module::
|
application module::
|
||||||
|
|
||||||
|
|
@ -215,4 +215,4 @@ You can also pass strings of SQL statements to the
|
||||||
(1, u'admin', u'admin@localhost')
|
(1, u'admin', u'admin@localhost')
|
||||||
|
|
||||||
For more information about SQLAlchemy, head over to the
|
For more information about SQLAlchemy, head over to the
|
||||||
`website <http://www.sqlalchemy.org/>`_.
|
`website <https://www.sqlalchemy.org/>`_.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
Using SQLite 3 with Flask
|
Using SQLite 3 with Flask
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
In Flask you can easily implement the opening of database connections on
|
In Flask you can easily implement the opening of database connections on
|
||||||
demand and closing them when the context dies (usually at the end of the
|
demand and closing them when the context dies (usually at the end of the
|
||||||
request).
|
request).
|
||||||
|
|
||||||
Here is a simple example of how you can use SQLite 3 with Flask::
|
Here is a simple example of how you can use SQLite 3 with Flask::
|
||||||
|
|
@ -71,7 +71,7 @@ Now in each request handling function you can access `g.db` to get the
|
||||||
current open database connection. To simplify working with SQLite, a
|
current open database connection. To simplify working with SQLite, a
|
||||||
row factory function is useful. It is executed for every result returned
|
row factory function is useful. It is executed for every result returned
|
||||||
from the database to convert the result. For instance, in order to get
|
from the database to convert the result. For instance, in order to get
|
||||||
dictionaries instead of tuples, this could be inserted into the ``get_db``
|
dictionaries instead of tuples, this could be inserted into the ``get_db``
|
||||||
function we created above::
|
function we created above::
|
||||||
|
|
||||||
def make_dicts(cursor, row):
|
def make_dicts(cursor, row):
|
||||||
|
|
@ -102,15 +102,15 @@ This would use Row objects rather than dicts to return the results of queries. T
|
||||||
|
|
||||||
Additionally, it is a good idea to provide a query function that combines
|
Additionally, it is a good idea to provide a query function that combines
|
||||||
getting the cursor, executing and fetching the results::
|
getting the cursor, executing and fetching the results::
|
||||||
|
|
||||||
def query_db(query, args=(), one=False):
|
def query_db(query, args=(), one=False):
|
||||||
cur = get_db().execute(query, args)
|
cur = get_db().execute(query, args)
|
||||||
rv = cur.fetchall()
|
rv = cur.fetchall()
|
||||||
cur.close()
|
cur.close()
|
||||||
return (rv[0] if rv else None) if one else rv
|
return (rv[0] if rv else None) if one else rv
|
||||||
|
|
||||||
This handy little function, in combination with a row factory, makes
|
This handy little function, in combination with a row factory, makes
|
||||||
working with the database much more pleasant than it is by just using the
|
working with the database much more pleasant than it is by just using the
|
||||||
raw cursor and connection objects.
|
raw cursor and connection objects.
|
||||||
|
|
||||||
Here is how you can use it::
|
Here is how you can use it::
|
||||||
|
|
@ -131,7 +131,7 @@ To pass variable parts to the SQL statement, use a question mark in the
|
||||||
statement and pass in the arguments as a list. Never directly add them to
|
statement and pass in the arguments as a list. Never directly add them to
|
||||||
the SQL statement with string formatting because this makes it possible
|
the SQL statement with string formatting because this makes it possible
|
||||||
to attack the application using `SQL Injections
|
to attack the application using `SQL Injections
|
||||||
<http://en.wikipedia.org/wiki/SQL_injection>`_.
|
<https://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||||
|
|
||||||
Initial Schemas
|
Initial Schemas
|
||||||
---------------
|
---------------
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ forms.
|
||||||
fun. You can get it from `PyPI
|
fun. You can get it from `PyPI
|
||||||
<https://pypi.python.org/pypi/Flask-WTF>`_.
|
<https://pypi.python.org/pypi/Flask-WTF>`_.
|
||||||
|
|
||||||
.. _Flask-WTF: http://pythonhosted.org/Flask-WTF/
|
.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/stable/
|
||||||
|
|
||||||
The Forms
|
The Forms
|
||||||
---------
|
---------
|
||||||
|
|
|
||||||
|
|
@ -102,9 +102,9 @@ docs to see the alternative method for running a server.
|
||||||
Invalid Import Name
|
Invalid Import Name
|
||||||
```````````````````
|
```````````````````
|
||||||
|
|
||||||
The ``FLASK_APP`` environment variable is the name of the module to import at
|
The ``FLASK_APP`` environment variable is the name of the module to import at
|
||||||
:command:`flask run`. In case that module is incorrectly named you will get an
|
:command:`flask run`. In case that module is incorrectly named you will get an
|
||||||
import error upon start (or if debug is enabled when you navigate to the
|
import error upon start (or if debug is enabled when you navigate to the
|
||||||
application). It will tell you what it tried to import and why it failed.
|
application). It will tell you what it tried to import and why it failed.
|
||||||
|
|
||||||
The most common reason is a typo or because you did not actually create an
|
The most common reason is a typo or because you did not actually create an
|
||||||
|
|
@ -159,14 +159,11 @@ Have another debugger in mind? See :ref:`working-with-debuggers`.
|
||||||
Routing
|
Routing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Modern web applications have beautiful URLs. This helps people remember
|
Modern web applications use meaningful URLs to help users. Users are more
|
||||||
the URLs, which is especially handy for applications that are used from
|
likely to like a page and come back if the page uses a meaningful URL they can
|
||||||
mobile devices with slower network connections. If the user can directly
|
remember and use to directly visit a page.
|
||||||
go to the desired page without having to hit the index page it is more
|
|
||||||
likely they will like the page and come back next time.
|
|
||||||
|
|
||||||
As you have seen above, the :meth:`~flask.Flask.route` decorator is used to
|
Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
|
||||||
bind a function to a URL. Here are some basic examples::
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -176,16 +173,16 @@ bind a function to a URL. Here are some basic examples::
|
||||||
def hello():
|
def hello():
|
||||||
return 'Hello, World'
|
return 'Hello, World'
|
||||||
|
|
||||||
But there is more to it! You can make certain parts of the URL dynamic and
|
You can do more! You can make parts of the URL dynamic and attach multiple
|
||||||
attach multiple rules to a function.
|
rules to a function.
|
||||||
|
|
||||||
Variable Rules
|
Variable Rules
|
||||||
``````````````
|
``````````````
|
||||||
|
|
||||||
To add variable parts to a URL you can mark these special sections as
|
You can add variable sections to a URL by marking sections with
|
||||||
``<variable_name>``. Such a part is then passed as a keyword argument to your
|
``<variable_name>``. Your function then receives the ``<variable_name>``
|
||||||
function. Optionally a converter can be used by specifying a rule with
|
as a keyword argument. Optionally, you can use a converter to specify the type
|
||||||
``<converter:variable_name>``. Here are some nice examples::
|
of the argument like ``<converter:variable_name>``. ::
|
||||||
|
|
||||||
@app.route('/user/<username>')
|
@app.route('/user/<username>')
|
||||||
def show_user_profile(username):
|
def show_user_profile(username):
|
||||||
|
|
@ -197,111 +194,111 @@ function. Optionally a converter can be used by specifying a rule with
|
||||||
# show the post with the given id, the id is an integer
|
# show the post with the given id, the id is an integer
|
||||||
return 'Post %d' % post_id
|
return 'Post %d' % post_id
|
||||||
|
|
||||||
The following converters exist:
|
@app.route('/path/<path:subpath>')
|
||||||
|
def show_subpath(subpath):
|
||||||
|
# show the subpath after /path/
|
||||||
|
return 'Subpath %s' % subpath
|
||||||
|
|
||||||
=========== ===============================================
|
Converter types:
|
||||||
`string` accepts any text without a slash (the default)
|
|
||||||
`int` accepts integers
|
|
||||||
`float` like ``int`` but for floating point values
|
|
||||||
`path` like the default but also accepts slashes
|
|
||||||
`any` matches one of the items provided
|
|
||||||
`uuid` accepts UUID strings
|
|
||||||
=========== ===============================================
|
|
||||||
|
|
||||||
.. admonition:: Unique URLs / Redirection Behavior
|
========== ==========================================
|
||||||
|
``string`` (default) accepts any text without a slash
|
||||||
|
``int`` accepts positive integers
|
||||||
|
``float`` accepts positive floating point values
|
||||||
|
``path`` like ``string`` but also accepts slashes
|
||||||
|
``uuid`` accepts UUID strings
|
||||||
|
========== ==========================================
|
||||||
|
|
||||||
Flask's URL rules are based on Werkzeug's routing module. The idea
|
Unique URLs / Redirection Behavior
|
||||||
behind that module is to ensure beautiful and unique URLs based on
|
``````````````````````````````````
|
||||||
precedents laid down by Apache and earlier HTTP servers.
|
|
||||||
|
|
||||||
Take these two rules::
|
Take these two rules::
|
||||||
|
|
||||||
@app.route('/projects/')
|
@app.route('/projects/')
|
||||||
def projects():
|
def projects():
|
||||||
return 'The project page'
|
return 'The project page'
|
||||||
|
|
||||||
@app.route('/about')
|
@app.route('/about')
|
||||||
def about():
|
def about():
|
||||||
return 'The about page'
|
return 'The about page'
|
||||||
|
|
||||||
Though they look rather similar, they differ in their use of the trailing
|
Though they look similar, they differ in their use of the trailing slash in
|
||||||
slash in the URL *definition*. In the first case, the canonical URL for the
|
the URL. In the first case, the canonical URL for the ``projects`` endpoint
|
||||||
``projects`` endpoint has a trailing slash. In that sense, it is similar to
|
uses a trailing slash. It's similar to a folder in a file system; if you
|
||||||
a folder on a filesystem. Accessing it without a trailing slash will cause
|
access the URL without a trailing slash, Flask redirects you to the
|
||||||
Flask to redirect to the canonical URL with the trailing slash.
|
canonical URL with the trailing slash.
|
||||||
|
|
||||||
In the second case, however, the URL is defined without a trailing slash,
|
In the second case, however, the URL definition lacks a trailing slash,
|
||||||
rather like the pathname of a file on UNIX-like systems. Accessing the URL
|
like the pathname of a file on UNIX-like systems. Accessing the URL with a
|
||||||
with a trailing slash will produce a 404 "Not Found" error.
|
trailing slash produces a 404 “Not Found” error.
|
||||||
|
|
||||||
This behavior allows relative URLs to continue working even if the trailing
|
|
||||||
slash is omitted, consistent with how Apache and other servers work. Also,
|
|
||||||
the URLs will stay unique, which helps search engines avoid indexing the
|
|
||||||
same page twice.
|
|
||||||
|
|
||||||
|
This behavior allows relative URLs to continue working even if the trailing
|
||||||
|
slash is omitted, consistent with how Apache and other servers work. Also,
|
||||||
|
the URLs will stay unique, which helps search engines avoid indexing the
|
||||||
|
same page twice.
|
||||||
|
|
||||||
.. _url-building:
|
.. _url-building:
|
||||||
|
|
||||||
URL Building
|
URL Building
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
If it can match URLs, can Flask also generate them? Of course it can. To
|
To build a URL to a specific function, use the :func:`~flask.url_for` function.
|
||||||
build a URL to a specific function you can use the :func:`~flask.url_for`
|
It accepts the name of the function as its first argument and any number of
|
||||||
function. It accepts the name of the function as first argument and a number
|
keyword arguments, each corresponding to a variable part of the URL rule.
|
||||||
of keyword arguments, each corresponding to the variable part of the URL rule.
|
Unknown variable parts are appended to the URL as query parameters.
|
||||||
Unknown variable parts are appended to the URL as query parameters. Here are
|
|
||||||
some examples::
|
Why would you want to build URLs using the URL reversing function
|
||||||
|
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
||||||
|
|
||||||
|
1. Reversing is often more descriptive than hard-coding the URLs.
|
||||||
|
2. You can change your URLs in one go instead of needing to remember to
|
||||||
|
manually change hard-coded URLs.
|
||||||
|
3. URL building handles escaping of special characters and Unicode data
|
||||||
|
transparently.
|
||||||
|
4. If your application is placed outside the URL root, for example, in
|
||||||
|
``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
|
||||||
|
handles that for you.
|
||||||
|
|
||||||
|
For example, here we use the :meth:`~flask.Flask.test_request_context` method
|
||||||
|
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
|
||||||
|
tells Flask to behave as though it's handling a request even while we use a
|
||||||
|
Python shell. See :ref:`context-locals`. ::
|
||||||
|
|
||||||
|
from flask import Flask, url_for
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'index'
|
||||||
|
|
||||||
|
@app.route('/login')
|
||||||
|
def login():
|
||||||
|
return 'login'
|
||||||
|
|
||||||
|
@app.route('/user/<username>')
|
||||||
|
def profile(username):
|
||||||
|
return '{}'s profile'.format(username)
|
||||||
|
|
||||||
|
with app.test_request_context():
|
||||||
|
print(url_for('index'))
|
||||||
|
print(url_for('login'))
|
||||||
|
print(url_for('login', next='/'))
|
||||||
|
print(url_for('profile', username='John Doe'))
|
||||||
|
|
||||||
>>> from flask import Flask, url_for
|
|
||||||
>>> app = Flask(__name__)
|
|
||||||
>>> @app.route('/')
|
|
||||||
... def index(): pass
|
|
||||||
...
|
|
||||||
>>> @app.route('/login')
|
|
||||||
... def login(): pass
|
|
||||||
...
|
|
||||||
>>> @app.route('/user/<username>')
|
|
||||||
... def profile(username): pass
|
|
||||||
...
|
|
||||||
>>> with app.test_request_context():
|
|
||||||
... print url_for('index')
|
|
||||||
... print url_for('login')
|
|
||||||
... print url_for('login', next='/')
|
|
||||||
... print url_for('profile', username='John Doe')
|
|
||||||
...
|
|
||||||
/
|
/
|
||||||
/login
|
/login
|
||||||
/login?next=/
|
/login?next=/
|
||||||
/user/John%20Doe
|
/user/John%20Doe
|
||||||
|
|
||||||
(This also uses the :meth:`~flask.Flask.test_request_context` method, explained
|
|
||||||
below. It tells Flask to behave as though it is handling a request, even
|
|
||||||
though we are interacting with it through a Python shell. Have a look at the
|
|
||||||
explanation below. :ref:`context-locals`).
|
|
||||||
|
|
||||||
Why would you want to build URLs using the URL reversing function
|
|
||||||
:func:`~flask.url_for` instead of hard-coding them into your templates?
|
|
||||||
There are three good reasons for this:
|
|
||||||
|
|
||||||
1. Reversing is often more descriptive than hard-coding the URLs. More
|
|
||||||
importantly, it allows you to change URLs in one go, without having to
|
|
||||||
remember to change URLs all over the place.
|
|
||||||
2. URL building will handle escaping of special characters and Unicode
|
|
||||||
data transparently for you, so you don't have to deal with them.
|
|
||||||
3. If your application is placed outside the URL root - say, in
|
|
||||||
``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle
|
|
||||||
that properly for you.
|
|
||||||
|
|
||||||
|
|
||||||
HTTP Methods
|
HTTP Methods
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
HTTP (the protocol web applications are speaking) knows different methods for
|
Web applications use different HTTP methods when accessing URLs. You should
|
||||||
accessing URLs. By default, a route only answers to ``GET`` requests, but that
|
familiarize yourself with the HTTP methods as you work with Flask. By default,
|
||||||
can be changed by providing the ``methods`` argument to the
|
a route only answers to ``GET`` requests. You can use the ``methods`` argument
|
||||||
:meth:`~flask.Flask.route` decorator. Here are some examples::
|
of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
|
||||||
|
::
|
||||||
from flask import request
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
|
|
@ -310,64 +307,11 @@ can be changed by providing the ``methods`` argument to the
|
||||||
else:
|
else:
|
||||||
show_the_login_form()
|
show_the_login_form()
|
||||||
|
|
||||||
If ``GET`` is present, ``HEAD`` will be added automatically for you. You
|
If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
|
||||||
don't have to deal with that. It will also make sure that ``HEAD`` requests
|
and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
|
||||||
are handled as the `HTTP RFC`_ (the document describing the HTTP
|
``OPTIONS`` is automatically implemented for you.
|
||||||
protocol) demands, so you can completely ignore that part of the HTTP
|
|
||||||
specification. Likewise, as of Flask 0.6, ``OPTIONS`` is implemented for you
|
|
||||||
automatically as well.
|
|
||||||
|
|
||||||
You have no idea what an HTTP method is? Worry not, here is a quick
|
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
|
||||||
introduction to HTTP methods and why they matter:
|
|
||||||
|
|
||||||
The HTTP method (also often called "the verb") tells the server what the
|
|
||||||
client wants to *do* with the requested page. The following methods are
|
|
||||||
very common:
|
|
||||||
|
|
||||||
``GET``
|
|
||||||
The browser tells the server to just *get* the information stored on
|
|
||||||
that page and send it. This is probably the most common method.
|
|
||||||
|
|
||||||
``HEAD``
|
|
||||||
The browser tells the server to get the information, but it is only
|
|
||||||
interested in the *headers*, not the content of the page. An
|
|
||||||
application is supposed to handle that as if a ``GET`` request was
|
|
||||||
received but to not deliver the actual content. In Flask you don't
|
|
||||||
have to deal with that at all, the underlying Werkzeug library handles
|
|
||||||
that for you.
|
|
||||||
|
|
||||||
``POST``
|
|
||||||
The browser tells the server that it wants to *post* some new
|
|
||||||
information to that URL and that the server must ensure the data is
|
|
||||||
stored and only stored once. This is how HTML forms usually
|
|
||||||
transmit data to the server.
|
|
||||||
|
|
||||||
``PUT``
|
|
||||||
Similar to ``POST`` but the server might trigger the store procedure
|
|
||||||
multiple times by overwriting the old values more than once. Now you
|
|
||||||
might be asking why this is useful, but there are some good reasons
|
|
||||||
to do it this way. Consider that the connection is lost during
|
|
||||||
transmission: in this situation a system between the browser and the
|
|
||||||
server might receive the request safely a second time without breaking
|
|
||||||
things. With ``POST`` that would not be possible because it must only
|
|
||||||
be triggered once.
|
|
||||||
|
|
||||||
``DELETE``
|
|
||||||
Remove the information at the given location.
|
|
||||||
|
|
||||||
``OPTIONS``
|
|
||||||
Provides a quick way for a client to figure out which methods are
|
|
||||||
supported by this URL. Starting with Flask 0.6, this is implemented
|
|
||||||
for you automatically.
|
|
||||||
|
|
||||||
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
|
||||||
form can submit to the server are ``GET`` and ``POST``. But with JavaScript
|
|
||||||
and future HTML standards you can use the other methods as well. Furthermore
|
|
||||||
HTTP has become quite popular lately and browsers are no longer the only
|
|
||||||
clients that are using HTTP. For instance, many revision control systems
|
|
||||||
use it.
|
|
||||||
|
|
||||||
.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt
|
|
||||||
|
|
||||||
Static Files
|
Static Files
|
||||||
------------
|
------------
|
||||||
|
|
@ -538,16 +482,16 @@ The Request Object
|
||||||
``````````````````
|
``````````````````
|
||||||
|
|
||||||
The request object is documented in the API section and we will not cover
|
The request object is documented in the API section and we will not cover
|
||||||
it here in detail (see :class:`~flask.request`). Here is a broad overview of
|
it here in detail (see :class:`~flask.Request`). Here is a broad overview of
|
||||||
some of the most common operations. First of all you have to import it from
|
some of the most common operations. First of all you have to import it from
|
||||||
the ``flask`` module::
|
the ``flask`` module::
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
The current request method is available by using the
|
The current request method is available by using the
|
||||||
:attr:`~flask.request.method` attribute. To access form data (data
|
:attr:`~flask.Request.method` attribute. To access form data (data
|
||||||
transmitted in a ``POST`` or ``PUT`` request) you can use the
|
transmitted in a ``POST`` or ``PUT`` request) you can use the
|
||||||
:attr:`~flask.request.form` attribute. Here is a full example of the two
|
:attr:`~flask.Request.form` attribute. Here is a full example of the two
|
||||||
attributes mentioned above::
|
attributes mentioned above::
|
||||||
|
|
||||||
@app.route('/login', methods=['POST', 'GET'])
|
@app.route('/login', methods=['POST', 'GET'])
|
||||||
|
|
@ -570,7 +514,7 @@ error page is shown instead. So for many situations you don't have to
|
||||||
deal with that problem.
|
deal with that problem.
|
||||||
|
|
||||||
To access parameters submitted in the URL (``?key=value``) you can use the
|
To access parameters submitted in the URL (``?key=value``) you can use the
|
||||||
:attr:`~flask.request.args` attribute::
|
:attr:`~flask.Request.args` attribute::
|
||||||
|
|
||||||
searchword = request.args.get('key', '')
|
searchword = request.args.get('key', '')
|
||||||
|
|
||||||
|
|
@ -579,7 +523,7 @@ We recommend accessing URL parameters with `get` or by catching the
|
||||||
bad request page in that case is not user friendly.
|
bad request page in that case is not user friendly.
|
||||||
|
|
||||||
For a full list of methods and attributes of the request object, head over
|
For a full list of methods and attributes of the request object, head over
|
||||||
to the :class:`~flask.request` documentation.
|
to the :class:`~flask.Request` documentation.
|
||||||
|
|
||||||
|
|
||||||
File Uploads
|
File Uploads
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ it JavaScript) into the context of a website. To remedy this, developers
|
||||||
have to properly escape text so that it cannot include arbitrary HTML
|
have to properly escape text so that it cannot include arbitrary HTML
|
||||||
tags. For more information on that have a look at the Wikipedia article
|
tags. For more information on that have a look at the Wikipedia article
|
||||||
on `Cross-Site Scripting
|
on `Cross-Site Scripting
|
||||||
<http://en.wikipedia.org/wiki/Cross-site_scripting>`_.
|
<https://en.wikipedia.org/wiki/Cross-site_scripting>`_.
|
||||||
|
|
||||||
Flask configures Jinja2 to automatically escape all values unless
|
Flask configures Jinja2 to automatically escape all values unless
|
||||||
explicitly told otherwise. This should rule out all XSS problems caused
|
explicitly told otherwise. This should rule out all XSS problems caused
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ Docstring conventions:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Module header:
|
Module header:
|
||||||
The module header consists of an utf-8 encoding declaration (if non
|
The module header consists of a utf-8 encoding declaration (if non
|
||||||
ASCII letters are used, but it is recommended all the time) and a
|
ASCII letters are used, but it is recommended all the time) and a
|
||||||
standard docstring::
|
standard docstring::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ In order to test the application, we add a second module
|
||||||
(:file:`flaskr_tests.py`) and create a unittest skeleton there::
|
(:file:`flaskr_tests.py`) and create a unittest skeleton there::
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import flaskr
|
from flaskr import flaskr
|
||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ In order to test the application, we add a second module
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||||
flaskr.app.config['TESTING'] = True
|
flaskr.app.testing = True
|
||||||
self.app = flaskr.app.test_client()
|
self.app = flaskr.app.test_client()
|
||||||
with flaskr.app.app_context():
|
with flaskr.app.app_context():
|
||||||
flaskr.init_db()
|
flaskr.init_db()
|
||||||
|
|
@ -98,8 +98,10 @@ test method to our class, like this::
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||||
|
flaskr.app.testing = True
|
||||||
self.app = flaskr.app.test_client()
|
self.app = flaskr.app.test_client()
|
||||||
flaskr.init_db()
|
with flaskr.app.app_context():
|
||||||
|
flaskr.init_db()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
os.close(self.db_fd)
|
os.close(self.db_fd)
|
||||||
|
|
@ -208,7 +210,7 @@ temporarily. With this you can access the :class:`~flask.request`,
|
||||||
functions. Here is a full example that demonstrates this approach::
|
functions. Here is a full example that demonstrates this approach::
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
with app.test_request_context('/?name=Peter'):
|
with app.test_request_context('/?name=Peter'):
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,4 @@ Here a screenshot of the final application:
|
||||||
|
|
||||||
Continue with :ref:`tutorial-folders`.
|
Continue with :ref:`tutorial-folders`.
|
||||||
|
|
||||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ into this file, :file:`flaskr/__init__.py`:
|
||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
from flaskr import app
|
from .flaskr import app
|
||||||
|
|
||||||
This import statement brings the application instance into the top-level
|
This import statement brings the application instance into the top-level
|
||||||
of the application package. When it is time to run the application, the
|
of the application package. When it is time to run the application, the
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ show_entries.html
|
||||||
This template extends the :file:`layout.html` template from above to display the
|
This template extends the :file:`layout.html` template from above to display the
|
||||||
messages. Note that the ``for`` loop iterates over the messages we passed
|
messages. Note that the ``for`` loop iterates over the messages we passed
|
||||||
in with the :func:`~flask.render_template` function. Notice that the form is
|
in with the :func:`~flask.render_template` function. Notice that the form is
|
||||||
configured to to submit to the `add_entry` view function and use ``POST`` as
|
configured to submit to the `add_entry` view function and use ``POST`` as
|
||||||
HTTP method:
|
HTTP method:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
@ -79,9 +79,9 @@ HTTP method:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul class=entries>
|
<ul class=entries>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
|
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><em>Unbelievable. No entries here so far</em>
|
<li><em>Unbelievable. No entries here so far</em></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ At this point you can run the tests. Here ``pytest`` will be used.
|
||||||
Run and watch the tests pass, within the top-level :file:`flaskr/`
|
Run and watch the tests pass, within the top-level :file:`flaskr/`
|
||||||
directory as::
|
directory as::
|
||||||
|
|
||||||
py.test
|
pytest
|
||||||
|
|
||||||
Testing + setuptools
|
Testing + setuptools
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ Any of the following is functionally equivalent::
|
||||||
response = send_file(open(fname), attachment_filename=fname)
|
response = send_file(open(fname), attachment_filename=fname)
|
||||||
response.set_etag(...)
|
response.set_etag(...)
|
||||||
|
|
||||||
The reason for this is that some file-like objects have a invalid or even
|
The reason for this is that some file-like objects have an invalid or even
|
||||||
misleading ``name`` attribute. Silently swallowing errors in such cases was not
|
misleading ``name`` attribute. Silently swallowing errors in such cases was not
|
||||||
a satisfying solution.
|
a satisfying solution.
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ when there is no request context yet but an application context. The old
|
||||||
``flask.Flask.request_globals_class`` attribute was renamed to
|
``flask.Flask.request_globals_class`` attribute was renamed to
|
||||||
:attr:`flask.Flask.app_ctx_globals_class`.
|
:attr:`flask.Flask.app_ctx_globals_class`.
|
||||||
|
|
||||||
.. _Flask-OldSessions: http://pythonhosted.org/Flask-OldSessions/
|
.. _Flask-OldSessions: https://pythonhosted.org/Flask-OldSessions/
|
||||||
|
|
||||||
Version 0.9
|
Version 0.9
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -198,7 +198,7 @@ applications with Flask. Because we want to make upgrading as easy as
|
||||||
possible we tried to counter the problems arising from these changes by
|
possible we tried to counter the problems arising from these changes by
|
||||||
providing a script that can ease the transition.
|
providing a script that can ease the transition.
|
||||||
|
|
||||||
The script scans your whole application and generates an unified diff with
|
The script scans your whole application and generates a unified diff with
|
||||||
changes it assumes are safe to apply. However as this is an automated
|
changes it assumes are safe to apply. However as this is an automated
|
||||||
tool it won't be able to find all use cases and it might miss some. We
|
tool it won't be able to find all use cases and it might miss some. We
|
||||||
internally spread a lot of deprecation warnings all over the place to make
|
internally spread a lot of deprecation warnings all over the place to make
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
from flaskr.flaskr import app
|
from .flaskr import app
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
[aliases]
|
[tool:pytest]
|
||||||
test=pytest
|
test=pytest
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,19 @@
|
||||||
export an MINITWIT_SETTINGS environment variable
|
export an MINITWIT_SETTINGS environment variable
|
||||||
pointing to a configuration file.
|
pointing to a configuration file.
|
||||||
|
|
||||||
2. tell flask about the right application:
|
2. install the app from the root of the project directory
|
||||||
|
|
||||||
|
pip install --editable .
|
||||||
|
|
||||||
|
3. tell flask about the right application:
|
||||||
|
|
||||||
export FLASK_APP=minitwit
|
export FLASK_APP=minitwit
|
||||||
|
|
||||||
2. fire up a shell and run this:
|
4. fire up a shell and run this:
|
||||||
|
|
||||||
flask initdb
|
flask initdb
|
||||||
|
|
||||||
3. now you can run minitwit:
|
5. now you can run minitwit:
|
||||||
|
|
||||||
flask run
|
flask run
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
from minitwit import app
|
from .minitwit import app
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ def format_datetime(timestamp):
|
||||||
|
|
||||||
def gravatar_url(email, size=80):
|
def gravatar_url(email, size=80):
|
||||||
"""Return the gravatar image for the given email address."""
|
"""Return the gravatar image for the given email address."""
|
||||||
return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
|
return 'https://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \
|
||||||
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
|
(md5(email.strip().lower().encode('utf-8')).hexdigest(), size)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
10
examples/patterns/largerapp/setup.py
Normal file
10
examples/patterns/largerapp/setup.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='yourapplication',
|
||||||
|
packages=['yourapplication'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
)
|
||||||
12
examples/patterns/largerapp/tests/test_largerapp.py
Normal file
12
examples/patterns/largerapp/tests/test_largerapp.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from yourapplication import app
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
app.config['TESTING'] = True
|
||||||
|
client = app.test_client()
|
||||||
|
return client
|
||||||
|
|
||||||
|
def test_index(client):
|
||||||
|
rv = client.get('/')
|
||||||
|
assert b"Hello World!" in rv.data
|
||||||
4
examples/patterns/largerapp/yourapplication/__init__.py
Normal file
4
examples/patterns/largerapp/yourapplication/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from flask import Flask
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
import yourapplication.views
|
||||||
5
examples/patterns/largerapp/yourapplication/views.py
Normal file
5
examples/patterns/largerapp/yourapplication/views.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello World!'
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.11.2-dev'
|
__version__ = '0.13-dev'
|
||||||
|
|
||||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||||
# in the module but are exported as public interface.
|
# in the module but are exported as public interface.
|
||||||
|
|
@ -40,7 +40,7 @@ from .signals import signals_available, template_rendered, request_started, \
|
||||||
# it.
|
# it.
|
||||||
from . import json
|
from . import json
|
||||||
|
|
||||||
# This was the only thing that flask used to export at one point and it had
|
# This was the only thing that Flask used to export at one point and it had
|
||||||
# a more generic name.
|
# a more generic name.
|
||||||
jsonify = json.jsonify
|
jsonify = json.jsonify
|
||||||
|
|
||||||
|
|
|
||||||
325
flask/app.py
325
flask/app.py
|
|
@ -10,31 +10,30 @@
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from threading import Lock
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import chain
|
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from collections import deque
|
from itertools import chain
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
from werkzeug.datastructures import ImmutableDict
|
from werkzeug.datastructures import ImmutableDict, Headers
|
||||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
from werkzeug.exceptions import BadRequest, HTTPException, \
|
||||||
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
InternalServerError, MethodNotAllowed, default_exceptions
|
||||||
MethodNotAllowed, BadRequest, default_exceptions
|
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
from . import cli, json
|
||||||
locked_cached_property, _endpoint_from_view_func, find_package, \
|
from ._compat import integer_types, reraise, string_types, text_type
|
||||||
get_debug_flag
|
from .config import Config, ConfigAttribute
|
||||||
from . import json, cli
|
from .ctx import AppContext, RequestContext, _AppCtxGlobals
|
||||||
from .wrappers import Request, Response
|
from .globals import _request_ctx_stack, g, request, session
|
||||||
from .config import ConfigAttribute, Config
|
from .helpers import _PackageBoundObject, \
|
||||||
from .ctx import RequestContext, AppContext, _AppCtxGlobals
|
_endpoint_from_view_func, find_package, get_debug_flag, \
|
||||||
from .globals import _request_ctx_stack, request, session, g
|
get_flashed_messages, locked_cached_property, url_for
|
||||||
from .sessions import SecureCookieSessionInterface
|
from .sessions import SecureCookieSessionInterface
|
||||||
|
from .signals import appcontext_tearing_down, got_request_exception, \
|
||||||
|
request_finished, request_started, request_tearing_down
|
||||||
from .templating import DispatchingJinjaLoader, Environment, \
|
from .templating import DispatchingJinjaLoader, Environment, \
|
||||||
_default_template_ctx_processor
|
_default_template_ctx_processor
|
||||||
from .signals import request_started, request_finished, got_request_exception, \
|
from .wrappers import Request, Response
|
||||||
request_tearing_down, appcontext_tearing_down
|
|
||||||
from ._compat import reraise, string_types, text_type, integer_types
|
|
||||||
|
|
||||||
# a lock used for logger initialization
|
# a lock used for logger initialization
|
||||||
_logger_lock = Lock()
|
_logger_lock = Lock()
|
||||||
|
|
@ -124,6 +123,9 @@ class Flask(_PackageBoundObject):
|
||||||
.. versionadded:: 0.11
|
.. versionadded:: 0.11
|
||||||
The `root_path` parameter was added.
|
The `root_path` parameter was added.
|
||||||
|
|
||||||
|
.. versionadded:: 0.13
|
||||||
|
The `host_matching` and `static_host` parameters were added.
|
||||||
|
|
||||||
:param import_name: the name of the application package
|
:param import_name: the name of the application package
|
||||||
:param static_url_path: can be used to specify a different path for the
|
:param static_url_path: can be used to specify a different path for the
|
||||||
static files on the web. Defaults to the name
|
static files on the web. Defaults to the name
|
||||||
|
|
@ -131,6 +133,13 @@ class Flask(_PackageBoundObject):
|
||||||
:param static_folder: the folder with static files that should be served
|
:param static_folder: the folder with static files that should be served
|
||||||
at `static_url_path`. Defaults to the ``'static'``
|
at `static_url_path`. Defaults to the ``'static'``
|
||||||
folder in the root path of the application.
|
folder in the root path of the application.
|
||||||
|
folder in the root path of the application. Defaults
|
||||||
|
to None.
|
||||||
|
:param host_matching: sets the app's ``url_map.host_matching`` to the given
|
||||||
|
given value. Defaults to False.
|
||||||
|
:param static_host: the host to use when adding the static route. Defaults
|
||||||
|
to None. Required when using ``host_matching=True``
|
||||||
|
with a ``static_folder`` configured.
|
||||||
:param template_folder: the folder that contains the templates that should
|
:param template_folder: the folder that contains the templates that should
|
||||||
be used by the application. Defaults to
|
be used by the application. Defaults to
|
||||||
``'templates'`` folder in the root path of the
|
``'templates'`` folder in the root path of the
|
||||||
|
|
@ -315,7 +324,7 @@ class Flask(_PackageBoundObject):
|
||||||
'PREFERRED_URL_SCHEME': 'http',
|
'PREFERRED_URL_SCHEME': 'http',
|
||||||
'JSON_AS_ASCII': True,
|
'JSON_AS_ASCII': True,
|
||||||
'JSON_SORT_KEYS': True,
|
'JSON_SORT_KEYS': True,
|
||||||
'JSONIFY_PRETTYPRINT_REGULAR': True,
|
'JSONIFY_PRETTYPRINT_REGULAR': False,
|
||||||
'JSONIFY_MIMETYPE': 'application/json',
|
'JSONIFY_MIMETYPE': 'application/json',
|
||||||
'TEMPLATES_AUTO_RELOAD': None,
|
'TEMPLATES_AUTO_RELOAD': None,
|
||||||
})
|
})
|
||||||
|
|
@ -338,7 +347,8 @@ class Flask(_PackageBoundObject):
|
||||||
session_interface = SecureCookieSessionInterface()
|
session_interface = SecureCookieSessionInterface()
|
||||||
|
|
||||||
def __init__(self, import_name, static_path=None, static_url_path=None,
|
def __init__(self, import_name, static_path=None, static_url_path=None,
|
||||||
static_folder='static', template_folder='templates',
|
static_folder='static', static_host=None,
|
||||||
|
host_matching=False, template_folder='templates',
|
||||||
instance_path=None, instance_relative_config=False,
|
instance_path=None, instance_relative_config=False,
|
||||||
root_path=None):
|
root_path=None):
|
||||||
_PackageBoundObject.__init__(self, import_name,
|
_PackageBoundObject.__init__(self, import_name,
|
||||||
|
|
@ -392,7 +402,7 @@ class Flask(_PackageBoundObject):
|
||||||
#: is the class for the instance check and the second the error handler
|
#: is the class for the instance check and the second the error handler
|
||||||
#: function.
|
#: function.
|
||||||
#:
|
#:
|
||||||
#: To register a error handler, use the :meth:`errorhandler`
|
#: To register an error handler, use the :meth:`errorhandler`
|
||||||
#: decorator.
|
#: decorator.
|
||||||
self.error_handler_spec = {None: self._error_handlers}
|
self.error_handler_spec = {None: self._error_handlers}
|
||||||
|
|
||||||
|
|
@ -405,17 +415,16 @@ class Flask(_PackageBoundObject):
|
||||||
#: .. versionadded:: 0.9
|
#: .. versionadded:: 0.9
|
||||||
self.url_build_error_handlers = []
|
self.url_build_error_handlers = []
|
||||||
|
|
||||||
#: A dictionary with lists of functions that should be called at the
|
#: A dictionary with lists of functions that will be called at the
|
||||||
#: beginning of the request. The key of the dictionary is the name of
|
#: beginning of each request. The key of the dictionary is the name of
|
||||||
#: the blueprint this function is active for, ``None`` for all requests.
|
#: the blueprint this function is active for, or ``None`` for all
|
||||||
#: This can for example be used to open database connections or
|
#: requests. To register a function, use the :meth:`before_request`
|
||||||
#: getting hold of the currently logged in user. To register a
|
#: decorator.
|
||||||
#: function here, use the :meth:`before_request` decorator.
|
|
||||||
self.before_request_funcs = {}
|
self.before_request_funcs = {}
|
||||||
|
|
||||||
#: A lists of functions that should be called at the beginning of the
|
#: A list of functions that will be called at the beginning of the
|
||||||
#: first request to this instance. To register a function here, use
|
#: first request to this instance. To register a function, use the
|
||||||
#: the :meth:`before_first_request` decorator.
|
#: :meth:`before_first_request` decorator.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.8
|
#: .. versionadded:: 0.8
|
||||||
self.before_first_request_funcs = []
|
self.before_first_request_funcs = []
|
||||||
|
|
@ -447,12 +456,11 @@ class Flask(_PackageBoundObject):
|
||||||
#: .. versionadded:: 0.9
|
#: .. versionadded:: 0.9
|
||||||
self.teardown_appcontext_funcs = []
|
self.teardown_appcontext_funcs = []
|
||||||
|
|
||||||
#: A dictionary with lists of functions that can be used as URL
|
#: A dictionary with lists of functions that are called before the
|
||||||
#: value processor functions. Whenever a URL is built these functions
|
#: :attr:`before_request_funcs` functions. The key of the dictionary is
|
||||||
#: are called to modify the dictionary of values in place. The key
|
#: the name of the blueprint this function is active for, or ``None``
|
||||||
#: ``None`` here is used for application wide
|
#: for all requests. To register a function, use
|
||||||
#: callbacks, otherwise the key is the name of the blueprint.
|
#: :meth:`url_value_preprocessor`.
|
||||||
#: Each of these functions has the chance to modify the dictionary
|
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.7
|
#: .. versionadded:: 0.7
|
||||||
self.url_value_preprocessors = {}
|
self.url_value_preprocessors = {}
|
||||||
|
|
@ -526,19 +534,22 @@ class Flask(_PackageBoundObject):
|
||||||
#: app.url_map.converters['list'] = ListConverter
|
#: app.url_map.converters['list'] = ListConverter
|
||||||
self.url_map = Map()
|
self.url_map = Map()
|
||||||
|
|
||||||
|
self.url_map.host_matching = host_matching
|
||||||
|
|
||||||
# tracks internally if the application already handled at least one
|
# tracks internally if the application already handled at least one
|
||||||
# request.
|
# request.
|
||||||
self._got_first_request = False
|
self._got_first_request = False
|
||||||
self._before_request_lock = Lock()
|
self._before_request_lock = Lock()
|
||||||
|
|
||||||
# register the static folder for the application. Do that even
|
# Add a static route using the provided static_url_path, static_host,
|
||||||
# if the folder does not exist. First of all it might be created
|
# and static_folder if there is a configured static_folder.
|
||||||
# while the server is running (usually happens during development)
|
# Note we do this without checking if static_folder exists.
|
||||||
# but also because google appengine stores static files somewhere
|
# For one, it might be created while the server is running (e.g. during
|
||||||
# else when mapped with the .yml file.
|
# development). Also, Google App Engine stores static files somewhere
|
||||||
if self.has_static_folder:
|
if self.has_static_folder:
|
||||||
|
assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
|
||||||
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||||
endpoint='static',
|
endpoint='static', host=static_host,
|
||||||
view_func=self.send_static_file)
|
view_func=self.send_static_file)
|
||||||
|
|
||||||
#: The click command line context for this application. Commands
|
#: The click command line context for this application. Commands
|
||||||
|
|
@ -814,7 +825,8 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
:param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
|
:param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
|
||||||
have the server available externally as well. Defaults to
|
have the server available externally as well. Defaults to
|
||||||
``'127.0.0.1'``.
|
``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config
|
||||||
|
variable if present.
|
||||||
:param port: the port of the webserver. Defaults to ``5000`` or the
|
:param port: the port of the webserver. Defaults to ``5000`` or the
|
||||||
port defined in the ``SERVER_NAME`` config variable if
|
port defined in the ``SERVER_NAME`` config variable if
|
||||||
present.
|
present.
|
||||||
|
|
@ -825,15 +837,22 @@ class Flask(_PackageBoundObject):
|
||||||
:func:`werkzeug.serving.run_simple` for more
|
:func:`werkzeug.serving.run_simple` for more
|
||||||
information.
|
information.
|
||||||
"""
|
"""
|
||||||
|
# Change this into a no-op if the server is invoked from the
|
||||||
|
# command line. Have a look at cli.py for more information.
|
||||||
|
if os.environ.get('FLASK_RUN_FROM_CLI_SERVER') == '1':
|
||||||
|
from .debughelpers import explain_ignored_app_run
|
||||||
|
explain_ignored_app_run()
|
||||||
|
return
|
||||||
|
|
||||||
from werkzeug.serving import run_simple
|
from werkzeug.serving import run_simple
|
||||||
if host is None:
|
_host = '127.0.0.1'
|
||||||
host = '127.0.0.1'
|
_port = 5000
|
||||||
if port is None:
|
server_name = self.config.get("SERVER_NAME")
|
||||||
server_name = self.config['SERVER_NAME']
|
sn_host, sn_port = None, None
|
||||||
if server_name and ':' in server_name:
|
if server_name:
|
||||||
port = int(server_name.rsplit(':', 1)[1])
|
sn_host, _, sn_port = server_name.partition(':')
|
||||||
else:
|
host = host or sn_host or _host
|
||||||
port = 5000
|
port = int(port or sn_port or _port)
|
||||||
if debug is not None:
|
if debug is not None:
|
||||||
self.debug = bool(debug)
|
self.debug = bool(debug)
|
||||||
options.setdefault('use_reloader', self.debug)
|
options.setdefault('use_reloader', self.debug)
|
||||||
|
|
@ -959,7 +978,7 @@ class Flask(_PackageBoundObject):
|
||||||
return iter(self._blueprint_order)
|
return iter(self._blueprint_order)
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
|
||||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||||
decorator. If a view_func is provided it will be registered with the
|
decorator. If a view_func is provided it will be registered with the
|
||||||
endpoint.
|
endpoint.
|
||||||
|
|
@ -999,6 +1018,10 @@ class Flask(_PackageBoundObject):
|
||||||
endpoint
|
endpoint
|
||||||
:param view_func: the function to call when serving a request to the
|
:param view_func: the function to call when serving a request to the
|
||||||
provided endpoint
|
provided endpoint
|
||||||
|
:param provide_automatic_options: controls whether the ``OPTIONS``
|
||||||
|
method should be added automatically. This can also be controlled
|
||||||
|
by setting the ``view_func.provide_automatic_options = False``
|
||||||
|
before adding the rule.
|
||||||
:param options: the options to be forwarded to the underlying
|
:param options: the options to be forwarded to the underlying
|
||||||
:class:`~werkzeug.routing.Rule` object. A change
|
:class:`~werkzeug.routing.Rule` object. A change
|
||||||
to Werkzeug is handling of method options. methods
|
to Werkzeug is handling of method options. methods
|
||||||
|
|
@ -1028,8 +1051,9 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
# starting with Flask 0.8 the view_func object can disable and
|
# starting with Flask 0.8 the view_func object can disable and
|
||||||
# force-enable the automatic options handling.
|
# force-enable the automatic options handling.
|
||||||
provide_automatic_options = getattr(view_func,
|
if provide_automatic_options is None:
|
||||||
'provide_automatic_options', None)
|
provide_automatic_options = getattr(view_func,
|
||||||
|
'provide_automatic_options', None)
|
||||||
|
|
||||||
if provide_automatic_options is None:
|
if provide_automatic_options is None:
|
||||||
if 'OPTIONS' not in methods:
|
if 'OPTIONS' not in methods:
|
||||||
|
|
@ -1153,7 +1177,8 @@ class Flask(_PackageBoundObject):
|
||||||
that do not necessarily have to be a subclass of the
|
that do not necessarily have to be a subclass of the
|
||||||
:class:`~werkzeug.exceptions.HTTPException` class.
|
:class:`~werkzeug.exceptions.HTTPException` class.
|
||||||
|
|
||||||
:param code: the code as integer for the handler
|
:param code_or_exception: the code as integer for the handler, or
|
||||||
|
an arbitrary exception
|
||||||
"""
|
"""
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
self._register_error_handler(None, code_or_exception, f)
|
self._register_error_handler(None, code_or_exception, f)
|
||||||
|
|
@ -1287,11 +1312,13 @@ class Flask(_PackageBoundObject):
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def before_request(self, f):
|
def before_request(self, f):
|
||||||
"""Registers a function to run before each request.
|
"""Registers a function to run before each request.
|
||||||
|
|
||||||
|
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.
|
The function will be called without any arguments. If it returns a
|
||||||
If the function returns a non-None value, it's handled as
|
non-None value, the value is handled as if it was the return value from
|
||||||
if it was the return value from the view and further
|
the view, and further request handling is stopped.
|
||||||
request handling is stopped.
|
|
||||||
"""
|
"""
|
||||||
self.before_request_funcs.setdefault(None, []).append(f)
|
self.before_request_funcs.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
|
@ -1347,7 +1374,7 @@ class Flask(_PackageBoundObject):
|
||||||
will have to surround the execution of these code by try/except
|
will have to surround the execution of these code by try/except
|
||||||
statements and log occurring errors.
|
statements and log occurring errors.
|
||||||
|
|
||||||
When a teardown function was called because of a exception it will
|
When a teardown function was called because of an exception it will
|
||||||
be passed an error object.
|
be passed an error object.
|
||||||
|
|
||||||
The return values of teardown functions are ignored.
|
The return values of teardown functions are ignored.
|
||||||
|
|
@ -1410,9 +1437,17 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def url_value_preprocessor(self, f):
|
def url_value_preprocessor(self, f):
|
||||||
"""Registers a function as URL value preprocessor for all view
|
"""Register a URL value preprocessor function for all view
|
||||||
functions of the application. It's called before the view functions
|
functions in the application. These functions will be called before the
|
||||||
are called and can modify the url values provided.
|
:meth:`before_request` functions.
|
||||||
|
|
||||||
|
The function can modify the values captured from the matched url before
|
||||||
|
they are passed to the view. For example, this can be used to pop a
|
||||||
|
common language code value and place it in ``g`` rather than pass it to
|
||||||
|
every view.
|
||||||
|
|
||||||
|
The function is passed the endpoint name and values dict. The return
|
||||||
|
value is ignored.
|
||||||
"""
|
"""
|
||||||
self.url_value_preprocessors.setdefault(None, []).append(f)
|
self.url_value_preprocessors.setdefault(None, []).append(f)
|
||||||
return f
|
return f
|
||||||
|
|
@ -1436,24 +1471,13 @@ class Flask(_PackageBoundObject):
|
||||||
def find_handler(handler_map):
|
def find_handler(handler_map):
|
||||||
if not handler_map:
|
if not handler_map:
|
||||||
return
|
return
|
||||||
queue = deque(exc_class.__mro__)
|
for cls in exc_class.__mro__:
|
||||||
# Protect from geniuses who might create circular references in
|
|
||||||
# __mro__
|
|
||||||
done = set()
|
|
||||||
|
|
||||||
while queue:
|
|
||||||
cls = queue.popleft()
|
|
||||||
if cls in done:
|
|
||||||
continue
|
|
||||||
done.add(cls)
|
|
||||||
handler = handler_map.get(cls)
|
handler = handler_map.get(cls)
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
# cache for next time exc_class is raised
|
# cache for next time exc_class is raised
|
||||||
handler_map[exc_class] = handler
|
handler_map[exc_class] = handler
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
queue.extend(cls.__mro__)
|
|
||||||
|
|
||||||
# try blueprint handlers
|
# try blueprint handlers
|
||||||
handler = find_handler(self.error_handler_spec
|
handler = find_handler(self.error_handler_spec
|
||||||
.get(request.blueprint, {})
|
.get(request.blueprint, {})
|
||||||
|
|
@ -1699,62 +1723,106 @@ class Flask(_PackageBoundObject):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def make_response(self, rv):
|
def make_response(self, rv):
|
||||||
"""Converts the return value from a view function to a real
|
"""Convert the return value from a view function to an instance of
|
||||||
response object that is an instance of :attr:`response_class`.
|
:attr:`response_class`.
|
||||||
|
|
||||||
The following types are allowed for `rv`:
|
:param rv: the return value from the view function. The view function
|
||||||
|
must return a response. Returning ``None``, or the view ending
|
||||||
|
without returning, is not allowed. The following types are allowed
|
||||||
|
for ``view_rv``:
|
||||||
|
|
||||||
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
|
``str`` (``unicode`` in Python 2)
|
||||||
|
A response object is created with the string encoded to UTF-8
|
||||||
======================= ===========================================
|
as the body.
|
||||||
:attr:`response_class` the object is returned unchanged
|
|
||||||
:class:`str` a response object is created with the
|
``bytes`` (``str`` in Python 2)
|
||||||
string as body
|
A response object is created with the bytes as the body.
|
||||||
:class:`unicode` a response object is created with the
|
|
||||||
string encoded to utf-8 as body
|
``tuple``
|
||||||
a WSGI function the function is called as WSGI application
|
Either ``(body, status, headers)``, ``(body, status)``, or
|
||||||
and buffered as response object
|
``(body, headers)``, where ``body`` is any of the other types
|
||||||
:class:`tuple` A tuple in the form ``(response, status,
|
allowed here, ``status`` is a string or an integer, and
|
||||||
headers)`` or ``(response, headers)``
|
``headers`` is a dictionary or a list of ``(key, value)``
|
||||||
where `response` is any of the
|
tuples. If ``body`` is a :attr:`response_class` instance,
|
||||||
types defined here, `status` is a string
|
``status`` overwrites the exiting value and ``headers`` are
|
||||||
or an integer and `headers` is a list or
|
extended.
|
||||||
a dictionary with header values.
|
|
||||||
======================= ===========================================
|
:attr:`response_class`
|
||||||
|
The object is returned unchanged.
|
||||||
:param rv: the return value from the view function
|
|
||||||
|
other :class:`~werkzeug.wrappers.Response` class
|
||||||
|
The object is coerced to :attr:`response_class`.
|
||||||
|
|
||||||
|
:func:`callable`
|
||||||
|
The function is called as a WSGI application. The result is
|
||||||
|
used to create a response object.
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
.. versionchanged:: 0.9
|
||||||
Previously a tuple was interpreted as the arguments for the
|
Previously a tuple was interpreted as the arguments for the
|
||||||
response object.
|
response object.
|
||||||
"""
|
"""
|
||||||
status_or_headers = headers = None
|
|
||||||
if isinstance(rv, tuple):
|
|
||||||
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
|
|
||||||
|
|
||||||
|
status = headers = None
|
||||||
|
|
||||||
|
# unpack tuple returns
|
||||||
|
if isinstance(rv, (tuple, list)):
|
||||||
|
len_rv = len(rv)
|
||||||
|
|
||||||
|
# a 3-tuple is unpacked directly
|
||||||
|
if len_rv == 3:
|
||||||
|
rv, status, headers = rv
|
||||||
|
# decide if a 2-tuple has status or headers
|
||||||
|
elif len_rv == 2:
|
||||||
|
if isinstance(rv[1], (Headers, dict, tuple, list)):
|
||||||
|
rv, headers = rv
|
||||||
|
else:
|
||||||
|
rv, status = rv
|
||||||
|
# other sized tuples are not allowed
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
'The view function did not return a valid response tuple.'
|
||||||
|
' The tuple must have the form (body, status, headers),'
|
||||||
|
' (body, status), or (body, headers).'
|
||||||
|
)
|
||||||
|
|
||||||
|
# the body must not be None
|
||||||
if rv is None:
|
if rv is None:
|
||||||
raise ValueError('View function did not return a response')
|
raise TypeError(
|
||||||
|
'The view function did not return a valid response. The'
|
||||||
if isinstance(status_or_headers, (dict, list)):
|
' function either returned None or ended without a return'
|
||||||
headers, status_or_headers = status_or_headers, None
|
' statement.'
|
||||||
|
)
|
||||||
|
|
||||||
|
# make sure the body is an instance of the response class
|
||||||
if not isinstance(rv, self.response_class):
|
if not isinstance(rv, self.response_class):
|
||||||
# When we create a response object directly, we let the constructor
|
|
||||||
# set the headers and status. We do this because there can be
|
|
||||||
# some extra logic involved when creating these objects with
|
|
||||||
# specific values (like default content type selection).
|
|
||||||
if isinstance(rv, (text_type, bytes, bytearray)):
|
if isinstance(rv, (text_type, bytes, bytearray)):
|
||||||
rv = self.response_class(rv, headers=headers,
|
# let the response class set the status and headers instead of
|
||||||
status=status_or_headers)
|
# waiting to do it manually, so that the class can handle any
|
||||||
headers = status_or_headers = None
|
# special logic
|
||||||
|
rv = self.response_class(rv, status=status, headers=headers)
|
||||||
|
status = headers = None
|
||||||
else:
|
else:
|
||||||
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:
|
# prefer the status if it was provided
|
||||||
if isinstance(status_or_headers, string_types):
|
if status is not None:
|
||||||
rv.status = status_or_headers
|
if isinstance(status, (text_type, bytes, bytearray)):
|
||||||
|
rv.status = status
|
||||||
else:
|
else:
|
||||||
rv.status_code = status_or_headers
|
rv.status_code = status
|
||||||
|
|
||||||
|
# extend existing headers with provided headers
|
||||||
if headers:
|
if headers:
|
||||||
rv.headers.extend(headers)
|
rv.headers.extend(headers)
|
||||||
|
|
||||||
|
|
@ -1817,16 +1885,16 @@ class Flask(_PackageBoundObject):
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def preprocess_request(self):
|
def preprocess_request(self):
|
||||||
"""Called before the actual request dispatching and will
|
"""Called before the request is dispatched. Calls
|
||||||
call each :meth:`before_request` decorated function, passing no
|
:attr:`url_value_preprocessors` registered with the app and the
|
||||||
arguments.
|
current blueprint (if any). Then calls :attr:`before_request_funcs`
|
||||||
If any of these functions returns a value, it's handled as
|
registered with the app and the blueprint.
|
||||||
if it was the return value from the view and further
|
|
||||||
request handling is stopped.
|
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
|
||||||
This also triggers the :meth:`url_value_preprocessor` functions before
|
further request handling is stopped.
|
||||||
the actual :meth:`before_request` functions are called.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bp = _request_ctx_stack.top.request.blueprint
|
bp = _request_ctx_stack.top.request.blueprint
|
||||||
|
|
||||||
funcs = self.url_value_preprocessors.get(None, ())
|
funcs = self.url_value_preprocessors.get(None, ())
|
||||||
|
|
@ -1986,14 +2054,17 @@ class Flask(_PackageBoundObject):
|
||||||
exception context to start the response
|
exception context to start the response
|
||||||
"""
|
"""
|
||||||
ctx = self.request_context(environ)
|
ctx = self.request_context(environ)
|
||||||
ctx.push()
|
|
||||||
error = None
|
error = None
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
ctx.push()
|
||||||
response = self.full_dispatch_request()
|
response = self.full_dispatch_request()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = e
|
error = e
|
||||||
response = self.handle_exception(e)
|
response = self.handle_exception(e)
|
||||||
|
except:
|
||||||
|
error = sys.exc_info()[1]
|
||||||
|
raise
|
||||||
return response(environ, start_response)
|
return response(environ, start_response)
|
||||||
finally:
|
finally:
|
||||||
if self.should_ignore_error(error):
|
if self.should_ignore_error(error):
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject):
|
||||||
warn_on_modifications = False
|
warn_on_modifications = False
|
||||||
_got_registered_once = False
|
_got_registered_once = False
|
||||||
|
|
||||||
|
#: Blueprint local JSON decoder class to use.
|
||||||
|
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
|
||||||
|
json_encoder = None
|
||||||
|
#: Blueprint local JSON decoder class to use.
|
||||||
|
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
|
||||||
|
json_decoder = None
|
||||||
|
|
||||||
def __init__(self, name, import_name, static_folder=None,
|
def __init__(self, name, import_name, static_folder=None,
|
||||||
static_url_path=None, template_folder=None,
|
static_url_path=None, template_folder=None,
|
||||||
url_prefix=None, subdomain=None, url_defaults=None,
|
url_prefix=None, subdomain=None, url_defaults=None,
|
||||||
|
|
|
||||||
89
flask/cli.py
89
flask/cli.py
|
|
@ -11,14 +11,18 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from threading import Lock, Thread
|
import traceback
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
from operator import attrgetter
|
||||||
|
from threading import Lock, Thread
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from ._compat import iteritems, reraise
|
|
||||||
from .helpers import get_debug_flag
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
from ._compat import iteritems, reraise
|
||||||
|
from .globals import current_app
|
||||||
|
from .helpers import get_debug_flag
|
||||||
|
|
||||||
|
|
||||||
class NoAppException(click.UsageError):
|
class NoAppException(click.UsageError):
|
||||||
"""Raised if an application cannot be found or loaded."""
|
"""Raised if an application cannot be found or loaded."""
|
||||||
|
|
@ -89,10 +93,18 @@ def locate_app(app_id):
|
||||||
try:
|
try:
|
||||||
__import__(module)
|
__import__(module)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise NoAppException('The file/path provided (%s) does not appear to '
|
# Reraise the ImportError if it occurred within the imported module.
|
||||||
'exist. Please verify the path is correct. If '
|
# Determine this by checking whether the trace has a depth > 1.
|
||||||
'app is not on PYTHONPATH, ensure the extension '
|
if sys.exc_info()[-1].tb_next:
|
||||||
'is .py' % module)
|
stack_trace = traceback.format_exc()
|
||||||
|
raise NoAppException('There was an error trying to import'
|
||||||
|
' the app (%s):\n%s' % (module, stack_trace))
|
||||||
|
else:
|
||||||
|
raise NoAppException('The file/path provided (%s) does not appear'
|
||||||
|
' to exist. Please verify the path is '
|
||||||
|
'correct. If app is not on PYTHONPATH, '
|
||||||
|
'ensure the extension is .py' % module)
|
||||||
|
|
||||||
mod = sys.modules[module]
|
mod = sys.modules[module]
|
||||||
if app_obj is None:
|
if app_obj is None:
|
||||||
app = find_best_app(mod)
|
app = find_best_app(mod)
|
||||||
|
|
@ -131,9 +143,9 @@ version_option = click.Option(['--version'],
|
||||||
is_flag=True, is_eager=True)
|
is_flag=True, is_eager=True)
|
||||||
|
|
||||||
class DispatchingApp(object):
|
class DispatchingApp(object):
|
||||||
"""Special application that dispatches to a flask application which
|
"""Special application that dispatches to a Flask application which
|
||||||
is imported by name in a background thread. If an error happens
|
is imported by name in a background thread. If an error happens
|
||||||
it is is recorded and shows as part of the WSGI handling which in case
|
it is recorded and shown as part of the WSGI handling which in case
|
||||||
of the Werkzeug debugger means that it shows up in the browser.
|
of the Werkzeug debugger means that it shows up in the browser.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -310,6 +322,7 @@ class FlaskGroup(AppGroup):
|
||||||
if add_default_commands:
|
if add_default_commands:
|
||||||
self.add_command(run_command)
|
self.add_command(run_command)
|
||||||
self.add_command(shell_command)
|
self.add_command(shell_command)
|
||||||
|
self.add_command(routes_command)
|
||||||
|
|
||||||
self._loaded_plugin_commands = False
|
self._loaded_plugin_commands = False
|
||||||
|
|
||||||
|
|
@ -362,7 +375,9 @@ class FlaskGroup(AppGroup):
|
||||||
# want the help page to break if the app does not exist.
|
# want the help page to break if the app does not exist.
|
||||||
# If someone attempts to use the command we try to create
|
# If someone attempts to use the command we try to create
|
||||||
# the app again and this will give us the error.
|
# the app again and this will give us the error.
|
||||||
pass
|
# However, we will not do so silently because that would confuse
|
||||||
|
# users.
|
||||||
|
traceback.print_exc()
|
||||||
return sorted(rv)
|
return sorted(rv)
|
||||||
|
|
||||||
def main(self, *args, **kwargs):
|
def main(self, *args, **kwargs):
|
||||||
|
|
@ -406,6 +421,13 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
||||||
"""
|
"""
|
||||||
from werkzeug.serving import run_simple
|
from werkzeug.serving import run_simple
|
||||||
|
|
||||||
|
# Set a global flag that indicates that we were invoked from the
|
||||||
|
# command line interface provided server command. This is detected
|
||||||
|
# by Flask.run to make the call into a no-op. This is necessary to
|
||||||
|
# avoid ugly errors when the script that is loaded here also attempts
|
||||||
|
# to start a server.
|
||||||
|
os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1'
|
||||||
|
|
||||||
debug = get_debug_flag()
|
debug = get_debug_flag()
|
||||||
if reload is None:
|
if reload is None:
|
||||||
reload = bool(debug)
|
reload = bool(debug)
|
||||||
|
|
@ -466,6 +488,53 @@ def shell_command():
|
||||||
code.interact(banner=banner, local=ctx)
|
code.interact(banner=banner, local=ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command('routes', short_help='Show the routes for the app.')
|
||||||
|
@click.option(
|
||||||
|
'--sort', '-s',
|
||||||
|
type=click.Choice(('endpoint', 'methods', 'rule', 'match')),
|
||||||
|
default='endpoint',
|
||||||
|
help=(
|
||||||
|
'Method to sort routes by. "match" is the order that Flask will match '
|
||||||
|
'routes when dispatching a request.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'--all-methods',
|
||||||
|
is_flag=True,
|
||||||
|
help="Show HEAD and OPTIONS methods."
|
||||||
|
)
|
||||||
|
@with_appcontext
|
||||||
|
def routes_command(sort, all_methods):
|
||||||
|
"""Show all registered routes with endpoints and methods."""
|
||||||
|
|
||||||
|
rules = list(current_app.url_map.iter_rules())
|
||||||
|
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
|
||||||
|
|
||||||
|
if sort in ('endpoint', 'rule'):
|
||||||
|
rules = sorted(rules, key=attrgetter(sort))
|
||||||
|
elif sort == 'methods':
|
||||||
|
rules = sorted(rules, key=lambda rule: sorted(rule.methods))
|
||||||
|
|
||||||
|
rule_methods = [
|
||||||
|
', '.join(sorted(rule.methods - ignored_methods)) for rule in rules
|
||||||
|
]
|
||||||
|
|
||||||
|
headers = ('Endpoint', 'Methods', 'Rule')
|
||||||
|
widths = (
|
||||||
|
max(len(rule.endpoint) for rule in rules),
|
||||||
|
max(len(methods) for methods in rule_methods),
|
||||||
|
max(len(rule.rule) for rule in rules),
|
||||||
|
)
|
||||||
|
widths = [max(len(h), w) for h, w in zip(headers, widths)]
|
||||||
|
row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths)
|
||||||
|
|
||||||
|
click.echo(row.format(*headers).strip())
|
||||||
|
click.echo(row.format(*('-' * width for width in widths)))
|
||||||
|
|
||||||
|
for rule, methods in zip(rules, rule_methods):
|
||||||
|
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
|
||||||
|
|
||||||
|
|
||||||
cli = FlaskGroup(help="""\
|
cli = FlaskGroup(help="""\
|
||||||
This shell command acts as general utility script for Flask applications.
|
This shell command acts as general utility script for Flask applications.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ class Config(dict):
|
||||||
d = types.ModuleType('config')
|
d = types.ModuleType('config')
|
||||||
d.__file__ = filename
|
d.__file__ = filename
|
||||||
try:
|
try:
|
||||||
with open(filename) as config_file:
|
with open(filename, mode='rb') as config_file:
|
||||||
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
:copyright: (c) 2015 by Armin Ronacher.
|
:copyright: (c) 2015 by Armin Ronacher.
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
from ._compat import implements_to_string, text_type
|
from ._compat import implements_to_string, text_type
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
|
|
@ -153,3 +156,12 @@ def explain_template_loading_attempts(app, template, attempts):
|
||||||
info.append(' See http://flask.pocoo.org/docs/blueprints/#templates')
|
info.append(' See http://flask.pocoo.org/docs/blueprints/#templates')
|
||||||
|
|
||||||
app.logger.info('\n'.join(info))
|
app.logger.info('\n'.join(info))
|
||||||
|
|
||||||
|
|
||||||
|
def explain_ignored_app_run():
|
||||||
|
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
||||||
|
warn(Warning('Silently ignoring app.run() because the '
|
||||||
|
'application is run from the flask command line '
|
||||||
|
'executable. Consider putting app.run() behind an '
|
||||||
|
'if __name__ == "__main__" guard to silence this '
|
||||||
|
'warning.'), stacklevel=3)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import posixpath
|
import posixpath
|
||||||
|
|
@ -17,6 +18,7 @@ import mimetypes
|
||||||
from time import time
|
from time import time
|
||||||
from zlib import adler32
|
from zlib import adler32
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
|
import unicodedata
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
|
@ -58,7 +60,7 @@ def get_debug_flag(default=None):
|
||||||
val = os.environ.get('FLASK_DEBUG')
|
val = os.environ.get('FLASK_DEBUG')
|
||||||
if not val:
|
if not val:
|
||||||
return default
|
return default
|
||||||
return val not in ('0', 'false', 'no')
|
return val.lower() not in ('0', 'false', 'no')
|
||||||
|
|
||||||
|
|
||||||
def _endpoint_from_view_func(view_func):
|
def _endpoint_from_view_func(view_func):
|
||||||
|
|
@ -330,6 +332,7 @@ def url_for(endpoint, **values):
|
||||||
values['_external'] = external
|
values['_external'] = external
|
||||||
values['_anchor'] = anchor
|
values['_anchor'] = anchor
|
||||||
values['_method'] = method
|
values['_method'] = method
|
||||||
|
values['_scheme'] = scheme
|
||||||
return appctx.app.handle_url_build_error(error, endpoint, values)
|
return appctx.app.handle_url_build_error(error, endpoint, values)
|
||||||
|
|
||||||
if anchor is not None:
|
if anchor is not None:
|
||||||
|
|
@ -477,8 +480,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
.. versionchanged:: 0.12
|
.. versionchanged:: 0.12
|
||||||
The `attachment_filename` is preferred over `filename` for MIME-type
|
The `attachment_filename` is preferred over `filename` for MIME-type
|
||||||
detection.
|
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`
|
This is relative to the :attr:`~Flask.root_path`
|
||||||
if a relative path is specified.
|
if a relative path is specified.
|
||||||
Alternatively a file object might be provided in
|
Alternatively a file object might be provided in
|
||||||
|
|
@ -534,8 +542,19 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
if attachment_filename is None:
|
if attachment_filename is None:
|
||||||
raise TypeError('filename unavailable, required for '
|
raise TypeError('filename unavailable, required for '
|
||||||
'sending as attachment')
|
'sending as attachment')
|
||||||
headers.add('Content-Disposition', 'attachment',
|
|
||||||
filename=attachment_filename)
|
try:
|
||||||
|
attachment_filename = attachment_filename.encode('latin-1')
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
filenames = {
|
||||||
|
'filename': unicodedata.normalize(
|
||||||
|
'NFKD', attachment_filename).encode('latin-1', 'ignore'),
|
||||||
|
'filename*': "UTF-8''%s" % url_quote(attachment_filename),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
filenames = {'filename': attachment_filename}
|
||||||
|
|
||||||
|
headers.add('Content-Disposition', 'attachment', **filenames)
|
||||||
|
|
||||||
if current_app.use_x_sendfile and filename:
|
if current_app.use_x_sendfile and filename:
|
||||||
if file is not None:
|
if file is not None:
|
||||||
|
|
@ -958,3 +977,24 @@ def total_seconds(td):
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
return td.days * 60 * 60 * 24 + td.seconds
|
return td.days * 60 * 60 * 24 + td.seconds
|
||||||
|
|
||||||
|
|
||||||
|
def is_ip(value):
|
||||||
|
"""Determine if the given string is an IP address.
|
||||||
|
|
||||||
|
:param value: value to check
|
||||||
|
:type value: str
|
||||||
|
|
||||||
|
:return: True if string is an IP address
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
for family in (socket.AF_INET, socket.AF_INET6):
|
||||||
|
try:
|
||||||
|
socket.inet_pton(family, value)
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
flask.jsonimpl
|
flask.json
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
Implementation helpers for the JSON support in Flask.
|
Implementation helpers for the JSON support in Flask.
|
||||||
|
|
||||||
|
|
@ -91,9 +91,16 @@ class JSONDecoder(_json.JSONDecoder):
|
||||||
def _dump_arg_defaults(kwargs):
|
def _dump_arg_defaults(kwargs):
|
||||||
"""Inject default arguments for dump functions."""
|
"""Inject default arguments for dump functions."""
|
||||||
if current_app:
|
if current_app:
|
||||||
kwargs.setdefault('cls', current_app.json_encoder)
|
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||||
|
kwargs.setdefault(
|
||||||
|
'cls',
|
||||||
|
bp.json_encoder if bp and bp.json_encoder
|
||||||
|
else current_app.json_encoder
|
||||||
|
)
|
||||||
|
|
||||||
if not current_app.config['JSON_AS_ASCII']:
|
if not current_app.config['JSON_AS_ASCII']:
|
||||||
kwargs.setdefault('ensure_ascii', False)
|
kwargs.setdefault('ensure_ascii', False)
|
||||||
|
|
||||||
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
||||||
else:
|
else:
|
||||||
kwargs.setdefault('sort_keys', True)
|
kwargs.setdefault('sort_keys', True)
|
||||||
|
|
@ -103,7 +110,12 @@ def _dump_arg_defaults(kwargs):
|
||||||
def _load_arg_defaults(kwargs):
|
def _load_arg_defaults(kwargs):
|
||||||
"""Inject default arguments for load functions."""
|
"""Inject default arguments for load functions."""
|
||||||
if current_app:
|
if current_app:
|
||||||
kwargs.setdefault('cls', current_app.json_decoder)
|
bp = current_app.blueprints.get(request.blueprint) if request else None
|
||||||
|
kwargs.setdefault(
|
||||||
|
'cls',
|
||||||
|
bp.json_decoder if bp and bp.json_decoder
|
||||||
|
else current_app.json_decoder
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
kwargs.setdefault('cls', JSONDecoder)
|
kwargs.setdefault('cls', JSONDecoder)
|
||||||
|
|
||||||
|
|
@ -236,11 +248,10 @@ def jsonify(*args, **kwargs):
|
||||||
Added support for serializing top-level arrays. This introduces a
|
Added support for serializing top-level arrays. This introduces a
|
||||||
security risk in ancient browsers. See :ref:`json-security` for details.
|
security risk in ancient browsers. See :ref:`json-security` for details.
|
||||||
|
|
||||||
This function's response will be pretty printed if it was not requested
|
This function's response will be pretty printed if the
|
||||||
with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless
|
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
|
||||||
the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false.
|
Flask app is running in debug mode. Compressed (not pretty) formatting
|
||||||
Compressed (not pretty) formatting currently means no indents and no
|
currently means no indents and no spaces after separators.
|
||||||
spaces after separators.
|
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
.. versionadded:: 0.2
|
||||||
"""
|
"""
|
||||||
|
|
@ -248,7 +259,7 @@ def jsonify(*args, **kwargs):
|
||||||
indent = None
|
indent = None
|
||||||
separators = (',', ':')
|
separators = (',', ':')
|
||||||
|
|
||||||
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_xhr:
|
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug:
|
||||||
indent = 2
|
indent = 2
|
||||||
separators = (', ', ': ')
|
separators = (', ', ': ')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,14 @@
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import warnings
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from werkzeug.http import http_date, parse_date
|
from werkzeug.http import http_date, parse_date
|
||||||
from werkzeug.datastructures import CallbackDict
|
from werkzeug.datastructures import CallbackDict
|
||||||
from . import Markup, json
|
from . import Markup, json
|
||||||
from ._compat import iteritems, text_type
|
from ._compat import iteritems, text_type
|
||||||
from .helpers import total_seconds
|
from .helpers import total_seconds, is_ip
|
||||||
|
|
||||||
from itsdangerous import URLSafeTimedSerializer, BadSignature
|
from itsdangerous import URLSafeTimedSerializer, BadSignature
|
||||||
|
|
||||||
|
|
@ -84,21 +85,25 @@ class TaggedJSONSerializer(object):
|
||||||
def dumps(self, value):
|
def dumps(self, value):
|
||||||
return json.dumps(_tag(value), separators=(',', ':'))
|
return json.dumps(_tag(value), separators=(',', ':'))
|
||||||
|
|
||||||
|
LOADS_MAP = {
|
||||||
|
' t': tuple,
|
||||||
|
' u': uuid.UUID,
|
||||||
|
' b': b64decode,
|
||||||
|
' m': Markup,
|
||||||
|
' d': parse_date,
|
||||||
|
}
|
||||||
|
|
||||||
def loads(self, value):
|
def loads(self, value):
|
||||||
def object_hook(obj):
|
def object_hook(obj):
|
||||||
if len(obj) != 1:
|
if len(obj) != 1:
|
||||||
return obj
|
return obj
|
||||||
the_key, the_value = next(iteritems(obj))
|
the_key, the_value = next(iteritems(obj))
|
||||||
if the_key == ' t':
|
# Check the key for a corresponding function
|
||||||
return tuple(the_value)
|
return_function = self.LOADS_MAP.get(the_key)
|
||||||
elif the_key == ' u':
|
if return_function:
|
||||||
return uuid.UUID(the_value)
|
# Pass the value to the function
|
||||||
elif the_key == ' b':
|
return return_function(the_value)
|
||||||
return b64decode(the_value)
|
# Didn't find a function for this object
|
||||||
elif the_key == ' m':
|
|
||||||
return Markup(the_value)
|
|
||||||
elif the_key == ' d':
|
|
||||||
return parse_date(the_value)
|
|
||||||
return obj
|
return obj
|
||||||
return json.loads(value, object_hook=object_hook)
|
return json.loads(value, object_hook=object_hook)
|
||||||
|
|
||||||
|
|
@ -168,7 +173,7 @@ class SessionInterface(object):
|
||||||
null_session_class = NullSession
|
null_session_class = NullSession
|
||||||
|
|
||||||
#: A flag that indicates if the session interface is pickle based.
|
#: A flag that indicates if the session interface is pickle based.
|
||||||
#: This can be used by flask extensions to make a decision in regards
|
#: This can be used by Flask extensions to make a decision in regards
|
||||||
#: to how to deal with the session object.
|
#: to how to deal with the session object.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 0.10
|
#: .. versionadded:: 0.10
|
||||||
|
|
@ -196,30 +201,62 @@ class SessionInterface(object):
|
||||||
return isinstance(obj, self.null_session_class)
|
return isinstance(obj, self.null_session_class)
|
||||||
|
|
||||||
def get_cookie_domain(self, app):
|
def get_cookie_domain(self, app):
|
||||||
"""Helpful helper method that returns the cookie domain that should
|
"""Returns the domain that should be set for the session cookie.
|
||||||
be used for the session cookie if session cookies are used.
|
|
||||||
|
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
|
||||||
|
falls back to detecting the domain based on ``SERVER_NAME``.
|
||||||
|
|
||||||
|
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
|
||||||
|
updated to avoid re-running the logic.
|
||||||
"""
|
"""
|
||||||
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
|
|
||||||
return app.config['SESSION_COOKIE_DOMAIN']
|
|
||||||
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
|
rv = app.config['SESSION_COOKIE_DOMAIN']
|
||||||
# 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
|
|
||||||
|
|
||||||
# If we infer the cookie domain from the server name we need
|
# set explicitly, or cached from SERVER_NAME detection
|
||||||
# to check if we are in a subpath. In that case we can't
|
# if False, return None
|
||||||
# set a cross domain cookie.
|
if rv is not None:
|
||||||
if rv is not None:
|
return rv if rv else None
|
||||||
path = self.get_cookie_path(app)
|
|
||||||
if path != '/':
|
|
||||||
rv = rv.lstrip('.')
|
|
||||||
|
|
||||||
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):
|
def get_cookie_path(self, app):
|
||||||
"""Returns the path for which the cookie should be valid. The
|
"""Returns the path for which the cookie should be valid. The
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ except ImportError:
|
||||||
temporarily_connected_to = connected_to = _fail
|
temporarily_connected_to = connected_to = _fail
|
||||||
del _fail
|
del _fail
|
||||||
|
|
||||||
# The namespace for code signals. If you are not flask code, do
|
# The namespace for code signals. If you are not Flask code, do
|
||||||
# not put signals in here. Create your own namespace instead.
|
# not put signals in here. Create your own namespace instead.
|
||||||
_signals = Namespace()
|
_signals = Namespace()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import werkzeug
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from werkzeug.test import Client, EnvironBuilder
|
from werkzeug.test import Client, EnvironBuilder
|
||||||
from flask import _request_ctx_stack
|
from flask import _request_ctx_stack
|
||||||
|
|
@ -43,11 +44,23 @@ class FlaskClient(Client):
|
||||||
information about how to use this class refer to
|
information about how to use this class refer to
|
||||||
:class:`werkzeug.test.Client`.
|
:class:`werkzeug.test.Client`.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.12
|
||||||
|
`app.test_client()` includes preset default environment, which can be
|
||||||
|
set after instantiation of the `app.test_client()` object in
|
||||||
|
`client.environ_base`.
|
||||||
|
|
||||||
Basic usage is outlined in the :ref:`testing` chapter.
|
Basic usage is outlined in the :ref:`testing` chapter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
preserve_context = False
|
preserve_context = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(FlaskClient, self).__init__(*args, **kwargs)
|
||||||
|
self.environ_base = {
|
||||||
|
"REMOTE_ADDR": "127.0.0.1",
|
||||||
|
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__
|
||||||
|
}
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def session_transaction(self, *args, **kwargs):
|
def session_transaction(self, *args, **kwargs):
|
||||||
"""When used in combination with a ``with`` statement this opens a
|
"""When used in combination with a ``with`` statement this opens a
|
||||||
|
|
@ -101,6 +114,7 @@ class FlaskClient(Client):
|
||||||
def open(self, *args, **kwargs):
|
def open(self, *args, **kwargs):
|
||||||
kwargs.setdefault('environ_overrides', {}) \
|
kwargs.setdefault('environ_overrides', {}) \
|
||||||
['flask._preserve_context'] = self.preserve_context
|
['flask._preserve_context'] = self.preserve_context
|
||||||
|
kwargs.setdefault('environ_base', self.environ_base)
|
||||||
|
|
||||||
as_tuple = kwargs.pop('as_tuple', False)
|
as_tuple = kwargs.pop('as_tuple', False)
|
||||||
buffered = kwargs.pop('buffered', False)
|
buffered = kwargs.pop('buffered', False)
|
||||||
|
|
|
||||||
|
|
@ -103,33 +103,34 @@ class View(object):
|
||||||
|
|
||||||
|
|
||||||
class MethodViewType(type):
|
class MethodViewType(type):
|
||||||
|
"""Metaclass for :class:`MethodView` that determines what methods the view
|
||||||
|
defines.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(cls, name, bases, d):
|
||||||
|
super(MethodViewType, cls).__init__(name, bases, d)
|
||||||
|
|
||||||
def __new__(cls, name, bases, d):
|
|
||||||
rv = type.__new__(cls, name, bases, d)
|
|
||||||
if 'methods' not in d:
|
if 'methods' not in d:
|
||||||
methods = set(rv.methods or [])
|
methods = set()
|
||||||
for key in d:
|
|
||||||
if key in http_method_funcs:
|
for key in http_method_funcs:
|
||||||
|
if hasattr(cls, key):
|
||||||
methods.add(key.upper())
|
methods.add(key.upper())
|
||||||
# If we have no method at all in there we don't want to
|
|
||||||
# add a method list. (This is for instance the case for
|
# If we have no method at all in there we don't want to add a
|
||||||
# the base class or another subclass of a base method view
|
# method list. This is for instance the case for the base class
|
||||||
# that does not introduce new methods).
|
# or another subclass of a base method view that does not introduce
|
||||||
|
# new methods.
|
||||||
if methods:
|
if methods:
|
||||||
rv.methods = sorted(methods)
|
cls.methods = methods
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class MethodView(with_metaclass(MethodViewType, View)):
|
class MethodView(with_metaclass(MethodViewType, View)):
|
||||||
"""Like a regular class-based view but that dispatches requests to
|
"""A class-based view that dispatches request methods to the corresponding
|
||||||
particular methods. For instance if you implement a method called
|
class methods. For example, if you implement a ``get`` method, it will be
|
||||||
:meth:`get` it means it will respond to ``'GET'`` requests and
|
used to handle ``GET`` requests. ::
|
||||||
the :meth:`dispatch_request` implementation will automatically
|
|
||||||
forward your request to that. Also :attr:`options` is set for you
|
|
||||||
automatically::
|
|
||||||
|
|
||||||
class CounterAPI(MethodView):
|
class CounterAPI(MethodView):
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return session.get('counter', 0)
|
return session.get('counter', 0)
|
||||||
|
|
||||||
|
|
@ -139,11 +140,14 @@ class MethodView(with_metaclass(MethodViewType, View)):
|
||||||
|
|
||||||
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def dispatch_request(self, *args, **kwargs):
|
def dispatch_request(self, *args, **kwargs):
|
||||||
meth = getattr(self, request.method.lower(), None)
|
meth = getattr(self, request.method.lower(), None)
|
||||||
|
|
||||||
# If the request method is HEAD and we don't have a handler for it
|
# If the request method is HEAD and we don't have a handler for it
|
||||||
# retry with GET.
|
# retry with GET.
|
||||||
if meth is None and request.method == 'HEAD':
|
if meth is None and request.method == 'HEAD':
|
||||||
meth = getattr(self, 'get', None)
|
meth = getattr(self, 'get', None)
|
||||||
|
|
||||||
assert meth is not None, 'Unimplemented method %r' % request.method
|
assert meth is not None, 'Unimplemented method %r' % request.method
|
||||||
return meth(*args, **kwargs)
|
return meth(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,8 @@ class Request(RequestBase):
|
||||||
on the request.
|
on the request.
|
||||||
"""
|
"""
|
||||||
rv = getattr(self, '_cached_json', _missing)
|
rv = getattr(self, '_cached_json', _missing)
|
||||||
if rv is not _missing:
|
# We return cached JSON only when the cache is enabled.
|
||||||
|
if cache and rv is not _missing:
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
if not (force or self.is_json):
|
if not (force or self.is_json):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
This command line script scans a whole application tree and attempts to
|
This command line script scans a whole application tree and attempts to
|
||||||
output an unified diff with all the changes that are necessary to easily
|
output a unified diff with all the changes that are necessary to easily
|
||||||
upgrade the application to 0.7 and to not yield deprecation warnings.
|
upgrade the application to 0.7 and to not yield deprecation warnings.
|
||||||
|
|
||||||
This will also attempt to find `after_request` functions that don't modify
|
This will also attempt to find `after_request` functions that don't modify
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
[aliases]
|
[aliases]
|
||||||
release = egg_info -RDb ''
|
release = egg_info -RDb ''
|
||||||
|
|
||||||
[wheel]
|
[bdist_wheel]
|
||||||
universal = 1
|
universal = 1
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
license_file = LICENSE
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
norecursedirs = .* *.egg *.egg-info env* artwork docs examples
|
norecursedirs = .* *.egg *.egg-info env* artwork docs
|
||||||
|
|
|
||||||
4
setup.py
4
setup.py
|
|
@ -41,7 +41,7 @@ Links
|
||||||
* `website <http://flask.pocoo.org/>`_
|
* `website <http://flask.pocoo.org/>`_
|
||||||
* `documentation <http://flask.pocoo.org/docs/>`_
|
* `documentation <http://flask.pocoo.org/docs/>`_
|
||||||
* `development version
|
* `development version
|
||||||
<http://github.com/pallets/flask/zipball/master#egg=Flask-dev>`_
|
<https://github.com/pallets/flask/zipball/master#egg=Flask-dev>`_
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
@ -59,7 +59,7 @@ with open('flask/__init__.py', 'rb') as f:
|
||||||
setup(
|
setup(
|
||||||
name='Flask',
|
name='Flask',
|
||||||
version=version,
|
version=version,
|
||||||
url='http://github.com/pallets/flask/',
|
url='https://github.com/pallets/flask/',
|
||||||
license='BSD',
|
license='BSD',
|
||||||
author='Armin Ronacher',
|
author='Armin Ronacher',
|
||||||
author_email='armin.ronacher@active-4.com',
|
author_email='armin.ronacher@active-4.com',
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
pytest
|
tox
|
||||||
|
|
|
||||||
7
tests/test_apps/cliapp/importerrorapp.py
Normal file
7
tests/test_apps/cliapp/importerrorapp.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
raise ImportError()
|
||||||
|
|
||||||
|
testapp = Flask('testapp')
|
||||||
|
|
@ -50,7 +50,7 @@ def test_options_on_multiple_rules():
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
|
||||||
|
|
||||||
|
|
||||||
def test_options_handling_disabled():
|
def test_provide_automatic_options_attr():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -70,6 +70,54 @@ def test_options_handling_disabled():
|
||||||
assert sorted(rv.allow) == ['OPTIONS']
|
assert sorted(rv.allow) == ['OPTIONS']
|
||||||
|
|
||||||
|
|
||||||
|
def test_provide_automatic_options_kwarg():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return flask.request.method
|
||||||
|
|
||||||
|
def more():
|
||||||
|
return flask.request.method
|
||||||
|
|
||||||
|
app.add_url_rule('/', view_func=index, provide_automatic_options=False)
|
||||||
|
app.add_url_rule(
|
||||||
|
'/more', view_func=more, methods=['GET', 'POST'],
|
||||||
|
provide_automatic_options=False
|
||||||
|
)
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/').data == b'GET'
|
||||||
|
|
||||||
|
rv = c.post('/')
|
||||||
|
assert rv.status_code == 405
|
||||||
|
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||||
|
|
||||||
|
# Older versions of Werkzeug.test.Client don't have an options method
|
||||||
|
if hasattr(c, 'options'):
|
||||||
|
rv = c.options('/')
|
||||||
|
else:
|
||||||
|
rv = c.open('/', method='OPTIONS')
|
||||||
|
|
||||||
|
assert rv.status_code == 405
|
||||||
|
|
||||||
|
rv = c.head('/')
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert not rv.data # head truncates
|
||||||
|
assert c.post('/more').data == b'POST'
|
||||||
|
assert c.get('/more').data == b'GET'
|
||||||
|
|
||||||
|
rv = c.delete('/more')
|
||||||
|
assert rv.status_code == 405
|
||||||
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
||||||
|
|
||||||
|
if hasattr(c, 'options'):
|
||||||
|
rv = c.options('/more')
|
||||||
|
else:
|
||||||
|
rv = c.open('/more', method='OPTIONS')
|
||||||
|
|
||||||
|
assert rv.status_code == 405
|
||||||
|
|
||||||
|
|
||||||
def test_request_dispatching():
|
def test_request_dispatching():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
|
@ -303,6 +351,42 @@ def test_session_using_session_settings():
|
||||||
assert 'httponly' not in cookie
|
assert 'httponly' not in cookie
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_localhost_warning(recwarn):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config.update(
|
||||||
|
SECRET_KEY='testing',
|
||||||
|
SERVER_NAME='localhost:5000',
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.session['testing'] = 42
|
||||||
|
return 'testing'
|
||||||
|
|
||||||
|
rv = app.test_client().get('/', 'http://localhost:5000/')
|
||||||
|
assert 'domain' not in rv.headers['set-cookie'].lower()
|
||||||
|
w = recwarn.pop(UserWarning)
|
||||||
|
assert '"localhost" is not a valid cookie domain' in str(w.message)
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_ip_warning(recwarn):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config.update(
|
||||||
|
SECRET_KEY='testing',
|
||||||
|
SERVER_NAME='127.0.0.1:5000',
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.session['testing'] = 42
|
||||||
|
return 'testing'
|
||||||
|
|
||||||
|
rv = app.test_client().get('/', 'http://127.0.0.1:5000/')
|
||||||
|
assert 'domain=127.0.0.1' in rv.headers['set-cookie'].lower()
|
||||||
|
w = recwarn.pop(UserWarning)
|
||||||
|
assert 'cookie domain is an IP' in str(w.message)
|
||||||
|
|
||||||
|
|
||||||
def test_missing_session():
|
def test_missing_session():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
|
@ -333,7 +417,7 @@ def test_session_expiration():
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
rv = client.get('/')
|
rv = client.get('/')
|
||||||
assert 'set-cookie' in rv.headers
|
assert 'set-cookie' in rv.headers
|
||||||
match = re.search(r'\bexpires=([^;]+)(?i)', rv.headers['set-cookie'])
|
match = re.search(r'(?i)\bexpires=([^;]+)', rv.headers['set-cookie'])
|
||||||
expires = parse_date(match.group())
|
expires = parse_date(match.group())
|
||||||
expected = datetime.utcnow() + app.permanent_session_lifetime
|
expected = datetime.utcnow() + app.permanent_session_lifetime
|
||||||
assert expires.year == expected.year
|
assert expires.year == expected.year
|
||||||
|
|
@ -791,6 +875,23 @@ def test_error_handling_processing():
|
||||||
assert resp.data == b'internal server error'
|
assert resp.data == b'internal server error'
|
||||||
|
|
||||||
|
|
||||||
|
def test_baseexception_error_handling():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config['LOGGER_HANDLER_POLICY'] = 'never'
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def broken_func():
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
|
||||||
|
with app.test_client() as c:
|
||||||
|
with pytest.raises(KeyboardInterrupt):
|
||||||
|
c.get('/')
|
||||||
|
|
||||||
|
ctx = flask._request_ctx_stack.top
|
||||||
|
assert ctx.preserved
|
||||||
|
assert type(ctx._preserved_exc) is KeyboardInterrupt
|
||||||
|
|
||||||
|
|
||||||
def test_before_request_and_routing_errors():
|
def test_before_request_and_routing_errors():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
|
@ -910,64 +1011,129 @@ def test_enctype_debug_helper():
|
||||||
assert 'This was submitted: "index.txt"' in str(e.value)
|
assert 'This was submitted: "index.txt"' in str(e.value)
|
||||||
|
|
||||||
|
|
||||||
def test_response_creation():
|
def test_response_types():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
|
||||||
@app.route('/unicode')
|
@app.route('/text')
|
||||||
def from_unicode():
|
def from_text():
|
||||||
return u'Hällo Wörld'
|
return u'Hällo Wörld'
|
||||||
|
|
||||||
@app.route('/string')
|
@app.route('/bytes')
|
||||||
def from_string():
|
def from_bytes():
|
||||||
return u'Hällo Wörld'.encode('utf-8')
|
return u'Hällo Wörld'.encode('utf-8')
|
||||||
|
|
||||||
@app.route('/args')
|
@app.route('/full_tuple')
|
||||||
def from_tuple():
|
def from_full_tuple():
|
||||||
return 'Meh', 400, {
|
return 'Meh', 400, {
|
||||||
'X-Foo': 'Testing',
|
'X-Foo': 'Testing',
|
||||||
'Content-Type': 'text/plain; charset=utf-8'
|
'Content-Type': 'text/plain; charset=utf-8'
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/two_args')
|
@app.route('/text_headers')
|
||||||
def from_two_args_tuple():
|
def from_text_headers():
|
||||||
return 'Hello', {
|
return 'Hello', {
|
||||||
'X-Foo': 'Test',
|
'X-Foo': 'Test',
|
||||||
'Content-Type': 'text/plain; charset=utf-8'
|
'Content-Type': 'text/plain; charset=utf-8'
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.route('/args_status')
|
@app.route('/text_status')
|
||||||
def from_status_tuple():
|
def from_text_status():
|
||||||
return 'Hi, status!', 400
|
return 'Hi, status!', 400
|
||||||
|
|
||||||
@app.route('/args_header')
|
@app.route('/response_headers')
|
||||||
def from_response_instance_status_tuple():
|
def from_response_headers():
|
||||||
return flask.Response('Hello world', 404), {
|
return flask.Response('Hello world', 404, {'X-Foo': 'Baz'}), {
|
||||||
"X-Foo": "Bar",
|
"X-Foo": "Bar",
|
||||||
"X-Bar": "Foo"
|
"X-Bar": "Foo"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@app.route('/response_status')
|
||||||
|
def from_response_status():
|
||||||
|
return app.response_class('Hello world', 400), 500
|
||||||
|
|
||||||
|
@app.route('/wsgi')
|
||||||
|
def from_wsgi():
|
||||||
|
return NotFound()
|
||||||
|
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8')
|
|
||||||
assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8')
|
assert c.get('/text').data == u'Hällo Wörld'.encode('utf-8')
|
||||||
rv = c.get('/args')
|
assert c.get('/bytes').data == u'Hällo Wörld'.encode('utf-8')
|
||||||
|
|
||||||
|
rv = c.get('/full_tuple')
|
||||||
assert rv.data == b'Meh'
|
assert rv.data == b'Meh'
|
||||||
assert rv.headers['X-Foo'] == 'Testing'
|
assert rv.headers['X-Foo'] == 'Testing'
|
||||||
assert rv.status_code == 400
|
assert rv.status_code == 400
|
||||||
assert rv.mimetype == 'text/plain'
|
assert rv.mimetype == 'text/plain'
|
||||||
rv2 = c.get('/two_args')
|
|
||||||
assert rv2.data == b'Hello'
|
rv = c.get('/text_headers')
|
||||||
assert rv2.headers['X-Foo'] == 'Test'
|
assert rv.data == b'Hello'
|
||||||
assert rv2.status_code == 200
|
assert rv.headers['X-Foo'] == 'Test'
|
||||||
assert rv2.mimetype == 'text/plain'
|
assert rv.status_code == 200
|
||||||
rv3 = c.get('/args_status')
|
assert rv.mimetype == 'text/plain'
|
||||||
assert rv3.data == b'Hi, status!'
|
|
||||||
assert rv3.status_code == 400
|
rv = c.get('/text_status')
|
||||||
assert rv3.mimetype == 'text/html'
|
assert rv.data == b'Hi, status!'
|
||||||
rv4 = c.get('/args_header')
|
assert rv.status_code == 400
|
||||||
assert rv4.data == b'Hello world'
|
assert rv.mimetype == 'text/html'
|
||||||
assert rv4.headers['X-Foo'] == 'Bar'
|
|
||||||
assert rv4.headers['X-Bar'] == 'Foo'
|
rv = c.get('/response_headers')
|
||||||
assert rv4.status_code == 404
|
assert rv.data == b'Hello world'
|
||||||
|
assert rv.headers.getlist('X-Foo') == ['Baz', 'Bar']
|
||||||
|
assert rv.headers['X-Bar'] == 'Foo'
|
||||||
|
assert rv.status_code == 404
|
||||||
|
|
||||||
|
rv = c.get('/response_status')
|
||||||
|
assert rv.data == b'Hello world'
|
||||||
|
assert rv.status_code == 500
|
||||||
|
|
||||||
|
rv = c.get('/wsgi')
|
||||||
|
assert b'Not Found' in rv.data
|
||||||
|
assert rv.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_response_type_errors():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
|
||||||
|
@app.route('/none')
|
||||||
|
def from_none():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.route('/small_tuple')
|
||||||
|
def from_small_tuple():
|
||||||
|
return 'Hello',
|
||||||
|
|
||||||
|
@app.route('/large_tuple')
|
||||||
|
def from_large_tuple():
|
||||||
|
return 'Hello', 234, {'X-Foo': 'Bar'}, '???'
|
||||||
|
|
||||||
|
@app.route('/bad_type')
|
||||||
|
def from_bad_type():
|
||||||
|
return True
|
||||||
|
|
||||||
|
@app.route('/bad_wsgi')
|
||||||
|
def from_bad_wsgi():
|
||||||
|
return lambda: None
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
|
||||||
|
with pytest.raises(TypeError) as e:
|
||||||
|
c.get('/none')
|
||||||
|
assert 'returned None' in str(e)
|
||||||
|
|
||||||
|
with pytest.raises(TypeError) as e:
|
||||||
|
c.get('/small_tuple')
|
||||||
|
assert 'tuple must have the form' in str(e)
|
||||||
|
|
||||||
|
pytest.raises(TypeError, c.get, '/large_tuple')
|
||||||
|
|
||||||
|
with pytest.raises(TypeError) as e:
|
||||||
|
c.get('/bad_type')
|
||||||
|
assert 'it was a bool' in str(e)
|
||||||
|
|
||||||
|
pytest.raises(TypeError, c.get, '/bad_wsgi')
|
||||||
|
|
||||||
|
|
||||||
def test_make_response():
|
def test_make_response():
|
||||||
|
|
@ -995,7 +1161,7 @@ def test_make_response_with_response_instance():
|
||||||
rv = flask.make_response(
|
rv = flask.make_response(
|
||||||
flask.jsonify({'msg': 'W00t'}), 400)
|
flask.jsonify({'msg': 'W00t'}), 400)
|
||||||
assert rv.status_code == 400
|
assert rv.status_code == 400
|
||||||
assert rv.data == b'{\n "msg": "W00t"\n}\n'
|
assert rv.data == b'{"msg":"W00t"}\n'
|
||||||
assert rv.mimetype == 'application/json'
|
assert rv.mimetype == 'application/json'
|
||||||
|
|
||||||
rv = flask.make_response(
|
rv = flask.make_response(
|
||||||
|
|
@ -1114,6 +1280,23 @@ def test_build_error_handler_reraise():
|
||||||
pytest.raises(BuildError, flask.url_for, 'not.existing')
|
pytest.raises(BuildError, flask.url_for, 'not.existing')
|
||||||
|
|
||||||
|
|
||||||
|
def test_url_for_passes_special_values_to_build_error_handler():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
@app.url_build_error_handlers.append
|
||||||
|
def handler(error, endpoint, values):
|
||||||
|
assert values == {
|
||||||
|
'_external': False,
|
||||||
|
'_anchor': None,
|
||||||
|
'_method': None,
|
||||||
|
'_scheme': None,
|
||||||
|
}
|
||||||
|
return 'handled'
|
||||||
|
|
||||||
|
with app.test_request_context():
|
||||||
|
flask.url_for('/')
|
||||||
|
|
||||||
|
|
||||||
def test_custom_converters():
|
def test_custom_converters():
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
|
|
||||||
|
|
@ -1171,20 +1354,23 @@ def test_static_url_path():
|
||||||
assert flask.url_for('static', filename='index.html') == '/foo/index.html'
|
assert flask.url_for('static', filename='index.html') == '/foo/index.html'
|
||||||
|
|
||||||
|
|
||||||
def test_none_response():
|
def test_static_route_with_host_matching():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__, host_matching=True, static_host='example.com')
|
||||||
app.testing = True
|
c = app.test_client()
|
||||||
|
rv = c.get('http://example.com/static/index.html')
|
||||||
@app.route('/')
|
assert rv.status_code == 200
|
||||||
def test():
|
rv.close()
|
||||||
return None
|
with app.test_request_context():
|
||||||
try:
|
rv = flask.url_for('static', filename='index.html', _external=True)
|
||||||
app.test_client().get('/')
|
assert rv == 'http://example.com/static/index.html'
|
||||||
except ValueError as e:
|
# Providing static_host without host_matching=True should error.
|
||||||
assert str(e) == 'View function did not return a response'
|
with pytest.raises(Exception):
|
||||||
pass
|
flask.Flask(__name__, static_host='example.com')
|
||||||
else:
|
# Providing host_matching=True with static_folder but without static_host should error.
|
||||||
assert "Expected ValueError"
|
with pytest.raises(Exception):
|
||||||
|
flask.Flask(__name__, host_matching=True)
|
||||||
|
# Providing host_matching=True without static_host but with static_folder=None should not error.
|
||||||
|
flask.Flask(__name__, host_matching=True, static_folder=None)
|
||||||
|
|
||||||
|
|
||||||
def test_request_locals():
|
def test_request_locals():
|
||||||
|
|
@ -1681,3 +1867,20 @@ def test_run_server_port(monkeypatch):
|
||||||
hostname, port = 'localhost', 8000
|
hostname, port = 'localhost', 8000
|
||||||
app.run(hostname, port, debug=True)
|
app.run(hostname, port, debug=True)
|
||||||
assert rv['result'] == 'running on %s:%s ...' % (hostname, port)
|
assert rv['result'] == 'running on %s:%s ...' % (hostname, port)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('host,port,expect_host,expect_port', (
|
||||||
|
(None, None, 'pocoo.org', 8080),
|
||||||
|
('localhost', None, 'localhost', 8080),
|
||||||
|
(None, 80, 'pocoo.org', 80),
|
||||||
|
('localhost', 80, 'localhost', 80),
|
||||||
|
))
|
||||||
|
def test_run_from_config(monkeypatch, host, port, expect_host, expect_port):
|
||||||
|
def run_simple_mock(hostname, port, *args, **kwargs):
|
||||||
|
assert hostname == expect_host
|
||||||
|
assert port == expect_port
|
||||||
|
|
||||||
|
monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock)
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config['SERVER_NAME'] = 'pocoo.org:8080'
|
||||||
|
app.run(host, port)
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,25 @@ def test_route_decorator_custom_endpoint_with_dots():
|
||||||
rv = c.get('/py/bar/123')
|
rv = c.get('/py/bar/123')
|
||||||
assert rv.status_code == 404
|
assert rv.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_endpoint_decorator():
|
||||||
|
from werkzeug.routing import Rule
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
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')
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/foo').data == b'bar'
|
||||||
|
assert c.get('/bp_prefix/bar').status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_template_filter():
|
def test_template_filter():
|
||||||
bp = flask.Blueprint('bp', __name__)
|
bp = flask.Blueprint('bp', __name__)
|
||||||
@bp.app_template_filter()
|
@bp.app_template_filter()
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,23 @@
|
||||||
from __future__ import absolute_import, print_function
|
from __future__ import absolute_import, print_function
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
from flask import Flask, current_app
|
from flask import Flask, current_app
|
||||||
|
|
||||||
from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
|
from flask.cli import cli, AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
|
||||||
find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \
|
find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \
|
||||||
find_default_import_path, get_version
|
find_default_import_path, get_version
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner():
|
||||||
|
return CliRunner()
|
||||||
|
|
||||||
|
|
||||||
def test_cli_name(test_apps):
|
def test_cli_name(test_apps):
|
||||||
"""Make sure the CLI object's name is the app's name and not the app itself"""
|
"""Make sure the CLI object's name is the app's name and not the app itself"""
|
||||||
from cliapp.app import testapp
|
from cliapp.app import testapp
|
||||||
|
|
@ -83,6 +89,7 @@ def test_locate_app(test_apps):
|
||||||
pytest.raises(NoAppException, locate_app, "notanpp.py")
|
pytest.raises(NoAppException, locate_app, "notanpp.py")
|
||||||
pytest.raises(NoAppException, locate_app, "cliapp/app")
|
pytest.raises(NoAppException, locate_app, "cliapp/app")
|
||||||
pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp")
|
pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp")
|
||||||
|
pytest.raises(NoAppException, locate_app, "cliapp.importerrorapp")
|
||||||
|
|
||||||
|
|
||||||
def test_find_default_import_path(test_apps, monkeypatch, tmpdir):
|
def test_find_default_import_path(test_apps, monkeypatch, tmpdir):
|
||||||
|
|
@ -128,7 +135,7 @@ def test_scriptinfo(test_apps):
|
||||||
assert obj.load_app() == app
|
assert obj.load_app() == app
|
||||||
|
|
||||||
|
|
||||||
def test_with_appcontext():
|
def test_with_appcontext(runner):
|
||||||
"""Test of with_appcontext."""
|
"""Test of with_appcontext."""
|
||||||
@click.command()
|
@click.command()
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
|
|
@ -137,13 +144,12 @@ def test_with_appcontext():
|
||||||
|
|
||||||
obj = ScriptInfo(create_app=lambda info: Flask("testapp"))
|
obj = ScriptInfo(create_app=lambda info: Flask("testapp"))
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
result = runner.invoke(testcmd, obj=obj)
|
result = runner.invoke(testcmd, obj=obj)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == 'testapp\n'
|
assert result.output == 'testapp\n'
|
||||||
|
|
||||||
|
|
||||||
def test_appgroup():
|
def test_appgroup(runner):
|
||||||
"""Test of with_appcontext."""
|
"""Test of with_appcontext."""
|
||||||
@click.group(cls=AppGroup)
|
@click.group(cls=AppGroup)
|
||||||
def cli():
|
def cli():
|
||||||
|
|
@ -163,7 +169,6 @@ def test_appgroup():
|
||||||
|
|
||||||
obj = ScriptInfo(create_app=lambda info: Flask("testappgroup"))
|
obj = ScriptInfo(create_app=lambda info: Flask("testappgroup"))
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
result = runner.invoke(cli, ['test'], obj=obj)
|
result = runner.invoke(cli, ['test'], obj=obj)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == 'testappgroup\n'
|
assert result.output == 'testappgroup\n'
|
||||||
|
|
@ -173,7 +178,7 @@ def test_appgroup():
|
||||||
assert result.output == 'testappgroup\n'
|
assert result.output == 'testappgroup\n'
|
||||||
|
|
||||||
|
|
||||||
def test_flaskgroup():
|
def test_flaskgroup(runner):
|
||||||
"""Test FlaskGroup."""
|
"""Test FlaskGroup."""
|
||||||
def create_app(info):
|
def create_app(info):
|
||||||
return Flask("flaskgroup")
|
return Flask("flaskgroup")
|
||||||
|
|
@ -186,7 +191,80 @@ def test_flaskgroup():
|
||||||
def test():
|
def test():
|
||||||
click.echo(current_app.name)
|
click.echo(current_app.name)
|
||||||
|
|
||||||
runner = CliRunner()
|
|
||||||
result = runner.invoke(cli, ['test'])
|
result = runner.invoke(cli, ['test'])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == 'flaskgroup\n'
|
assert result.output == 'flaskgroup\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_print_exceptions(runner):
|
||||||
|
"""Print the stacktrace if the CLI."""
|
||||||
|
def create_app(info):
|
||||||
|
raise Exception("oh no")
|
||||||
|
return Flask("flaskgroup")
|
||||||
|
|
||||||
|
@click.group(cls=FlaskGroup, create_app=create_app)
|
||||||
|
def cli(**params):
|
||||||
|
pass
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ['--help'])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert 'Exception: oh no' in result.output
|
||||||
|
assert 'Traceback' in result.output
|
||||||
|
|
||||||
|
|
||||||
|
class TestRoutes:
|
||||||
|
@pytest.fixture
|
||||||
|
def invoke(self, runner):
|
||||||
|
def create_app(info):
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
|
||||||
|
@app.route('/get_post/<int:x>/<int:y>', methods=['GET', 'POST'])
|
||||||
|
def yyy_get_post(x, y):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.route('/zzz_post', methods=['POST'])
|
||||||
|
def aaa_post():
|
||||||
|
pass
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
cli = FlaskGroup(create_app=create_app)
|
||||||
|
return partial(runner.invoke, cli)
|
||||||
|
|
||||||
|
def expect_order(self, order, output):
|
||||||
|
# skip the header and match the start of each row
|
||||||
|
for expect, line in zip(order, output.splitlines()[2:]):
|
||||||
|
# do this instead of startswith for nicer pytest output
|
||||||
|
assert line[:len(expect)] == expect
|
||||||
|
|
||||||
|
def test_simple(self, invoke):
|
||||||
|
result = invoke(['routes'])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
self.expect_order(
|
||||||
|
['aaa_post', 'static', 'yyy_get_post'],
|
||||||
|
result.output
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sort(self, invoke):
|
||||||
|
default_output = invoke(['routes']).output
|
||||||
|
endpoint_output = invoke(['routes', '-s', 'endpoint']).output
|
||||||
|
assert default_output == endpoint_output
|
||||||
|
self.expect_order(
|
||||||
|
['static', 'yyy_get_post', 'aaa_post'],
|
||||||
|
invoke(['routes', '-s', 'methods']).output
|
||||||
|
)
|
||||||
|
self.expect_order(
|
||||||
|
['yyy_get_post', 'static', 'aaa_post'],
|
||||||
|
invoke(['routes', '-s', 'rule']).output
|
||||||
|
)
|
||||||
|
self.expect_order(
|
||||||
|
['aaa_post', 'yyy_get_post', 'static'],
|
||||||
|
invoke(['routes', '-s', 'match']).output
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_all_methods(self, invoke):
|
||||||
|
output = invoke(['routes']).output
|
||||||
|
assert 'GET, HEAD, OPTIONS, POST' not in output
|
||||||
|
output = invoke(['routes', '--all-methods']).output
|
||||||
|
assert 'GET, HEAD, OPTIONS, POST' in output
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,14 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import os
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import os
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
from flask._compat import PY2
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
# config keys used for the TestConfig
|
# config keys used for the TestConfig
|
||||||
|
|
@ -187,3 +190,18 @@ def test_get_namespace():
|
||||||
assert 2 == len(bar_options)
|
assert 2 == len(bar_options)
|
||||||
assert 'bar stuff 1' == bar_options['BAR_STUFF_1']
|
assert 'bar stuff 1' == bar_options['BAR_STUFF_1']
|
||||||
assert 'bar stuff 2' == bar_options['BAR_STUFF_2']
|
assert 'bar stuff 2' == bar_options['BAR_STUFF_2']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('encoding', ['utf-8', 'iso-8859-15', 'latin-1'])
|
||||||
|
def test_from_pyfile_weird_encoding(tmpdir, encoding):
|
||||||
|
f = tmpdir.join('my_config.py')
|
||||||
|
f.write_binary(textwrap.dedent(u'''
|
||||||
|
# -*- coding: {0} -*-
|
||||||
|
TEST_VALUE = "föö"
|
||||||
|
'''.format(encoding)).encode(encoding))
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config.from_pyfile(str(f))
|
||||||
|
value = app.config['TEST_VALUE']
|
||||||
|
if PY2:
|
||||||
|
value = value.decode(encoding)
|
||||||
|
assert value == u'föö'
|
||||||
|
|
|
||||||
|
|
@ -179,8 +179,8 @@ def test_flaskext_broken_package_no_module_caching(flaskext_broken):
|
||||||
def test_no_error_swallowing(flaskext_broken):
|
def test_no_error_swallowing(flaskext_broken):
|
||||||
with pytest.raises(ImportError) as excinfo:
|
with pytest.raises(ImportError) as excinfo:
|
||||||
import flask.ext.broken
|
import flask.ext.broken
|
||||||
|
# python3.6 raises a subclass of ImportError: 'ModuleNotFoundError'
|
||||||
assert excinfo.type is ImportError
|
assert issubclass(excinfo.type, ImportError)
|
||||||
if PY2:
|
if PY2:
|
||||||
message = 'No module named missing_module'
|
message = 'No module named missing_module'
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,14 @@ def has_encoding(name):
|
||||||
|
|
||||||
class TestJSON(object):
|
class TestJSON(object):
|
||||||
|
|
||||||
|
def test_ignore_cached_json(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
with app.test_request_context('/', method='POST', data='malformed',
|
||||||
|
content_type='application/json'):
|
||||||
|
assert flask.request.get_json(silent=True, cache=True) is None
|
||||||
|
with pytest.raises(BadRequest):
|
||||||
|
flask.request.get_json(silent=False, cache=False)
|
||||||
|
|
||||||
def test_post_empty_json_adds_exception_to_response_content_in_debug(self):
|
def test_post_empty_json_adds_exception_to_response_content_in_debug(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.config['DEBUG'] = True
|
app.config['DEBUG'] = True
|
||||||
|
|
@ -113,20 +121,17 @@ class TestJSON(object):
|
||||||
rv = flask.json.load(out)
|
rv = flask.json.load(out)
|
||||||
assert rv == test_data
|
assert rv == test_data
|
||||||
|
|
||||||
def test_jsonify_basic_types(self):
|
@pytest.mark.parametrize('test_value', [0, -1, 1, 23, 3.14, 's', "longer string", True, False, None])
|
||||||
|
def test_jsonify_basic_types(self, test_value):
|
||||||
"""Test jsonify with basic types."""
|
"""Test jsonify with basic types."""
|
||||||
# Should be able to use pytest parametrize on this, but I couldn't
|
|
||||||
# figure out the correct syntax
|
|
||||||
# https://pytest.org/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions
|
|
||||||
test_data = (0, 1, 23, 3.14, 's', "longer string", True, False,)
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
for i, d in enumerate(test_data):
|
|
||||||
url = '/jsonify_basic_types{0}'.format(i)
|
url = '/jsonify_basic_types'
|
||||||
app.add_url_rule(url, str(i), lambda x=d: flask.jsonify(x))
|
app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x))
|
||||||
rv = c.get(url)
|
rv = c.get(url)
|
||||||
assert rv.mimetype == 'application/json'
|
assert rv.mimetype == 'application/json'
|
||||||
assert flask.json.loads(rv.data) == d
|
assert flask.json.loads(rv.data) == test_value
|
||||||
|
|
||||||
def test_jsonify_dicts(self):
|
def test_jsonify_dicts(self):
|
||||||
"""Test jsonify with dicts and kwargs unpacking."""
|
"""Test jsonify with dicts and kwargs unpacking."""
|
||||||
|
|
@ -170,12 +175,10 @@ class TestJSON(object):
|
||||||
|
|
||||||
def test_jsonify_date_types(self):
|
def test_jsonify_date_types(self):
|
||||||
"""Test jsonify with datetime.date and datetime.datetime types."""
|
"""Test jsonify with datetime.date and datetime.datetime types."""
|
||||||
|
|
||||||
test_dates = (
|
test_dates = (
|
||||||
datetime.datetime(1973, 3, 11, 6, 30, 45),
|
datetime.datetime(1973, 3, 11, 6, 30, 45),
|
||||||
datetime.date(1975, 1, 5)
|
datetime.date(1975, 1, 5)
|
||||||
)
|
)
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
|
|
||||||
|
|
@ -189,8 +192,7 @@ class TestJSON(object):
|
||||||
def test_jsonify_uuid_types(self):
|
def test_jsonify_uuid_types(self):
|
||||||
"""Test jsonify with uuid.UUID types"""
|
"""Test jsonify with uuid.UUID types"""
|
||||||
|
|
||||||
test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4)
|
test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF' * 4)
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
url = '/uuid_test'
|
url = '/uuid_test'
|
||||||
app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid))
|
app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid))
|
||||||
|
|
@ -265,6 +267,47 @@ class TestJSON(object):
|
||||||
}), content_type='application/json')
|
}), content_type='application/json')
|
||||||
assert rv.data == b'"<42>"'
|
assert rv.data == b'"<42>"'
|
||||||
|
|
||||||
|
def test_blueprint_json_customization(self):
|
||||||
|
class X(object):
|
||||||
|
def __init__(self, val):
|
||||||
|
self.val = val
|
||||||
|
|
||||||
|
class MyEncoder(flask.json.JSONEncoder):
|
||||||
|
def default(self, o):
|
||||||
|
if isinstance(o, X):
|
||||||
|
return '<%d>' % o.val
|
||||||
|
|
||||||
|
return flask.json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
class MyDecoder(flask.json.JSONDecoder):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs.setdefault('object_hook', self.object_hook)
|
||||||
|
flask.json.JSONDecoder.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def object_hook(self, obj):
|
||||||
|
if len(obj) == 1 and '_foo' in obj:
|
||||||
|
return X(obj['_foo'])
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
bp = flask.Blueprint('bp', __name__)
|
||||||
|
bp.json_encoder = MyEncoder
|
||||||
|
bp.json_decoder = MyDecoder
|
||||||
|
|
||||||
|
@bp.route('/bp', methods=['POST'])
|
||||||
|
def index():
|
||||||
|
return flask.json.dumps(flask.request.get_json()['x'])
|
||||||
|
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
rv = c.post('/bp', data=flask.json.dumps({
|
||||||
|
'x': {'_foo': 42}
|
||||||
|
}), content_type='application/json')
|
||||||
|
assert rv.data == b'"<42>"'
|
||||||
|
|
||||||
def test_modified_url_encoding(self):
|
def test_modified_url_encoding(self):
|
||||||
class ModifiedRequest(flask.Request):
|
class ModifiedRequest(flask.Request):
|
||||||
url_charset = 'euc-kr'
|
url_charset = 'euc-kr'
|
||||||
|
|
@ -287,6 +330,8 @@ class TestJSON(object):
|
||||||
def test_json_key_sorting(self):
|
def test_json_key_sorting(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.testing = True
|
app.testing = True
|
||||||
|
app.debug = True
|
||||||
|
|
||||||
assert app.config['JSON_SORT_KEYS'] == True
|
assert app.config['JSON_SORT_KEYS'] == True
|
||||||
d = dict.fromkeys(range(20), 'foo')
|
d = dict.fromkeys(range(20), 'foo')
|
||||||
|
|
||||||
|
|
@ -513,7 +558,7 @@ class TestSendfile(object):
|
||||||
assert rv.status_code == 416
|
assert rv.status_code == 416
|
||||||
rv.close()
|
rv.close()
|
||||||
|
|
||||||
last_modified = datetime.datetime.fromtimestamp(os.path.getmtime(
|
last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime(
|
||||||
os.path.join(app.root_path, 'static/index.html'))).replace(
|
os.path.join(app.root_path, 'static/index.html'))).replace(
|
||||||
microsecond=0)
|
microsecond=0)
|
||||||
|
|
||||||
|
|
@ -536,10 +581,11 @@ class TestSendfile(object):
|
||||||
value, options = \
|
value, options = \
|
||||||
parse_options_header(rv.headers['Content-Disposition'])
|
parse_options_header(rv.headers['Content-Disposition'])
|
||||||
assert value == 'attachment'
|
assert value == 'attachment'
|
||||||
|
assert options['filename'] == 'index.html'
|
||||||
|
assert 'filename*' not in rv.headers['Content-Disposition']
|
||||||
rv.close()
|
rv.close()
|
||||||
|
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
assert options['filename'] == 'index.html'
|
|
||||||
rv = flask.send_file('static/index.html', as_attachment=True)
|
rv = flask.send_file('static/index.html', as_attachment=True)
|
||||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||||
assert value == 'attachment'
|
assert value == 'attachment'
|
||||||
|
|
@ -556,6 +602,19 @@ class TestSendfile(object):
|
||||||
assert options['filename'] == 'index.txt'
|
assert options['filename'] == 'index.txt'
|
||||||
rv.close()
|
rv.close()
|
||||||
|
|
||||||
|
def test_attachment_with_utf8_filename(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
with app.test_request_context():
|
||||||
|
rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=u'Ñandú/pingüino.txt')
|
||||||
|
content_disposition = set(rv.headers['Content-Disposition'].split('; '))
|
||||||
|
assert content_disposition == set((
|
||||||
|
'attachment',
|
||||||
|
'filename="Nandu/pinguino.txt"',
|
||||||
|
"filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"
|
||||||
|
))
|
||||||
|
rv.close()
|
||||||
|
|
||||||
def test_static_file(self):
|
def test_static_file(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
# default cache timeout is 12 hours
|
# default cache timeout is 12 hours
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
from flask.sessions import SessionInterface
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from greenlet import greenlet
|
from greenlet import greenlet
|
||||||
|
|
@ -193,3 +194,27 @@ def test_greenlet_context_copying_api():
|
||||||
|
|
||||||
result = greenlets[0].run()
|
result = greenlets[0].run()
|
||||||
assert result == 42
|
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():
|
||||||
|
# shouldn't get here
|
||||||
|
assert False
|
||||||
|
|
||||||
|
response = app.test_client().get('/')
|
||||||
|
assert response.status_code == 500
|
||||||
|
assert not flask.request
|
||||||
|
assert not flask.current_app
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
import werkzeug
|
||||||
|
|
||||||
from flask._compat import text_type
|
from flask._compat import text_type
|
||||||
|
|
||||||
|
|
@ -43,6 +44,40 @@ def test_environ_defaults():
|
||||||
rv = c.get('/')
|
rv = c.get('/')
|
||||||
assert rv.data == b'http://localhost/'
|
assert rv.data == b'http://localhost/'
|
||||||
|
|
||||||
|
def test_environ_base_default():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
@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__
|
||||||
|
|
||||||
|
def test_environ_base_modified():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.testing = True
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.g.user_agent = flask.request.headers["User-Agent"]
|
||||||
|
return flask.request.remote_addr
|
||||||
|
|
||||||
|
with app.test_client() as c:
|
||||||
|
c.environ_base['REMOTE_ADDR'] = '0.0.0.0'
|
||||||
|
c.environ_base['HTTP_USER_AGENT'] = 'Foo'
|
||||||
|
rv = c.get('/')
|
||||||
|
assert rv.data == b'0.0.0.0'
|
||||||
|
assert flask.g.user_agent == 'Foo'
|
||||||
|
|
||||||
|
c.environ_base['REMOTE_ADDR'] = '0.0.0.1'
|
||||||
|
c.environ_base['HTTP_USER_AGENT'] = 'Bar'
|
||||||
|
rv = c.get('/')
|
||||||
|
assert rv.data == b'0.0.0.1'
|
||||||
|
assert flask.g.user_agent == 'Bar'
|
||||||
|
|
||||||
def test_redirect_keep_session():
|
def test_redirect_keep_session():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.secret_key = 'testing'
|
app.secret_key = 'testing'
|
||||||
|
|
|
||||||
|
|
@ -160,3 +160,45 @@ def test_endpoint_override():
|
||||||
|
|
||||||
# But these tests should still pass. We just log a warning.
|
# But these tests should still pass. We just log a warning.
|
||||||
common_test(app)
|
common_test(app)
|
||||||
|
|
||||||
|
def test_multiple_inheritance():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/').data == b'GET'
|
||||||
|
assert c.delete('/').data == b'DELETE'
|
||||||
|
assert sorted(GetDeleteView.methods) == ['DELETE', 'GET']
|
||||||
|
|
||||||
|
def test_remove_method_from_parent():
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
assert c.get('/').data == b'GET'
|
||||||
|
assert c.post('/').status_code == 405
|
||||||
|
assert sorted(View.methods) == ['GET']
|
||||||
|
|
|
||||||
12
tox.ini
12
tox.ini
|
|
@ -1,14 +1,20 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35}-{release,devel}{,-simplejson}
|
envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35,py36}-{release,devel}{,-simplejson}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
passenv = LANG
|
||||||
|
usedevelop=true
|
||||||
commands =
|
commands =
|
||||||
py.test []
|
# 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=
|
deps=
|
||||||
pytest
|
pytest
|
||||||
|
pytest-cov
|
||||||
greenlet
|
greenlet
|
||||||
|
|
||||||
lowest: Werkzeug==0.7
|
lowest: Werkzeug==0.7
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue