Merge branch 'master' into figome-multiple-inheritance
This commit is contained in:
commit
0d9d3d8f92
115 changed files with 1453 additions and 740 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
CHANGES merge=union
|
||||||
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
|
||||||
|
|
|
||||||
59
CHANGES
59
CHANGES
|
|
@ -3,14 +3,67 @@ 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`_).
|
||||||
|
|
||||||
|
.. _#1489: https://github.com/pallets/flask/pull/1489
|
||||||
|
.. _#2017: https://github.com/pallets/flask/pull/2017
|
||||||
|
.. _#2223: https://github.com/pallets/flask/pull/2223
|
||||||
|
|
||||||
|
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 for ``send_file`` has been removed, as per issue ``#104``.
|
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
|
||||||
See pull request ``#1849``.
|
has been removed, as per issue ``#104``. See pull request ``#1849``.
|
||||||
|
- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall back to
|
||||||
|
``application/octet-stream``. See pull request ``#1988``.
|
||||||
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join``
|
||||||
(pull request ``#1730``).
|
(pull request ``#1730``).
|
||||||
|
- Revert a behavior change that made the dev server crash instead of returning
|
||||||
|
a Internal Server Error (pull request ``#2006``).
|
||||||
|
- Correctly invoke response handlers for both regular request dispatching as
|
||||||
|
well as error handlers.
|
||||||
|
- Disable logger propagation by default for the app logger.
|
||||||
|
- Add support for range requests in ``send_file``.
|
||||||
|
- ``app.test_client`` includes preset default environment, which can now be
|
||||||
|
directly set, instead of per ``client.get``.
|
||||||
|
|
||||||
Version 0.11.2
|
Version 0.11.2
|
||||||
--------------
|
--------------
|
||||||
|
|
@ -325,7 +378,7 @@ Released on September 29th 2011, codename Rakija
|
||||||
- Applications now not only have a root path where the resources and modules
|
- Applications now not only have a root path where the resources and modules
|
||||||
are located but also an instance path which is the designated place to
|
are located but also an instance path which is the designated place to
|
||||||
drop files that are modified at runtime (uploads etc.). Also this is
|
drop files that are modified at runtime (uploads etc.). Also this is
|
||||||
conceptionally only instance depending and outside version control so it's
|
conceptually only instance depending and outside version control so it's
|
||||||
the perfect place to put configuration files etc. For more information
|
the perfect place to put configuration files etc. For more information
|
||||||
see :ref:`instance-folders`.
|
see :ref:`instance-folders`.
|
||||||
- Added the ``APPLICATION_ROOT`` configuration variable.
|
- Added the ``APPLICATION_ROOT`` configuration variable.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
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>
|
||||||
|
|
|
||||||
68
docs/api.rst
68
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
|
||||||
|
|
@ -316,13 +260,7 @@ Useful Functions and Classes
|
||||||
|
|
||||||
.. autofunction:: url_for
|
.. autofunction:: url_for
|
||||||
|
|
||||||
.. function:: abort(code)
|
.. autofunction:: abort
|
||||||
|
|
||||||
Raises an :exc:`~werkzeug.exceptions.HTTPException` for the given
|
|
||||||
status code. For example to abort request handling with a page not
|
|
||||||
found exception, you would call ``abort(404)``.
|
|
||||||
|
|
||||||
:param code: the HTTP error code.
|
|
||||||
|
|
||||||
.. autofunction:: redirect
|
.. autofunction:: redirect
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
20
docs/cli.rst
20
docs/cli.rst
|
|
@ -56,14 +56,24 @@ If you are constantly working with a virtualenv you can also put the
|
||||||
bottom of the file. That way every time you activate your virtualenv you
|
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
|
||||||
----------
|
----------
|
||||||
|
|
||||||
The :command:`flask` script can also be instructed to enable the debug
|
The :command:`flask` script can also be instructed to enable the debug
|
||||||
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
mode of the application automatically by exporting ``FLASK_DEBUG``. If
|
||||||
set to ``1`` debug is enabled or ``0`` disables it.
|
set to ``1`` debug is enabled or ``0`` disables it::
|
||||||
|
|
||||||
Or with a filename::
|
|
||||||
|
|
||||||
export FLASK_DEBUG=1
|
export FLASK_DEBUG=1
|
||||||
|
|
||||||
|
|
@ -141,8 +151,8 @@ This could be a file named :file:`autoapp.py` with these contents::
|
||||||
from yourapplication import create_app
|
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
|
||||||
|
|
|
||||||
11
docs/conf.py
11
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
|
||||||
|
|
@ -49,7 +52,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 +234,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 +260,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
|
||||||
|
|
@ -177,12 +193,10 @@ The following configuration values are used internally by Flask:
|
||||||
behavior by changing this variable.
|
behavior by changing this variable.
|
||||||
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 cachability.
|
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
|
||||||
|
|
||||||
|
|
@ -130,12 +130,12 @@ to httpd 2.4 syntax
|
||||||
Require all granted
|
Require all granted
|
||||||
|
|
||||||
|
|
||||||
For more information consult the `mod_wsgi wiki`_.
|
For more information consult the `mod_wsgi documentation`_.
|
||||||
|
|
||||||
.. _mod_wsgi: http://code.google.com/p/modwsgi/
|
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
|
||||||
.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide
|
.. _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 wiki: http://code.google.com/p/modwsgi/w/list
|
.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ Given a flask application in myapp.py, use the following command:
|
||||||
|
|
||||||
.. sourcecode:: text
|
.. sourcecode:: text
|
||||||
|
|
||||||
$ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app
|
$ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app
|
||||||
|
|
||||||
The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi,
|
The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi,
|
||||||
since its smarter about that. It is used together with the ``--mount`` directive
|
since its smarter about that. It is used together with the ``--mount`` directive
|
||||||
|
|
@ -66,7 +66,7 @@ to have it in the URL root its a bit simpler::
|
||||||
uwsgi_pass unix:/tmp/yourapplication.sock;
|
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
|
||||||
|
|
@ -51,7 +51,7 @@ And then add this to your Flask app::
|
||||||
from raven.contrib.flask import Sentry
|
from raven.contrib.flask import Sentry
|
||||||
sentry = Sentry(app, dsn='YOUR_DSN_HERE')
|
sentry = Sentry(app, dsn='YOUR_DSN_HERE')
|
||||||
|
|
||||||
Of if you are using factories you can also init it later::
|
Or if you are using factories you can also init it later::
|
||||||
|
|
||||||
from raven.contrib.flask import Sentry
|
from raven.contrib.flask import Sentry
|
||||||
sentry = Sentry(dsn='YOUR_DSN_HERE')
|
sentry = Sentry(dsn='YOUR_DSN_HERE')
|
||||||
|
|
@ -77,7 +77,7 @@ You might want to show custom error pages to the user when an error occurs.
|
||||||
This can be done by registering error handlers.
|
This can be done by registering error handlers.
|
||||||
|
|
||||||
Error handlers are normal :ref:`views` but instead of being registered for
|
Error handlers are normal :ref:`views` but instead of being registered for
|
||||||
routes they are registered for exceptions that are rised while trying to
|
routes, they are registered for exceptions that are raised while trying to
|
||||||
do something else.
|
do something else.
|
||||||
|
|
||||||
Registering
|
Registering
|
||||||
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -40,24 +40,20 @@ installations of Python, one for each project. It doesn't actually install
|
||||||
separate copies of Python, but it does provide a clever way to keep different
|
separate copies of Python, but it does provide a clever way to keep different
|
||||||
project environments isolated. Let's see how virtualenv works.
|
project environments isolated. Let's see how virtualenv works.
|
||||||
|
|
||||||
If you are on Mac OS X or Linux, chances are that one of the following two
|
If you are on Mac OS X or Linux, chances are that the following
|
||||||
commands will work for you::
|
command will work for you::
|
||||||
|
|
||||||
$ sudo easy_install virtualenv
|
|
||||||
|
|
||||||
or even better::
|
|
||||||
|
|
||||||
$ sudo pip install virtualenv
|
$ sudo pip install virtualenv
|
||||||
|
|
||||||
One of these will probably install virtualenv on your system. Maybe it's even
|
It will probably install virtualenv on your system. Maybe it's even
|
||||||
in your package manager. If you use Ubuntu, try::
|
in your package manager. If you use Ubuntu, try::
|
||||||
|
|
||||||
$ sudo apt-get install python-virtualenv
|
$ sudo apt-get install python-virtualenv
|
||||||
|
|
||||||
If you are on Windows and don't have the :command:`easy_install` command, you must
|
If you are on Windows and don't have the ``easy_install`` command, you must
|
||||||
install it first. Check the :ref:`windows-easy-install` section for more
|
install it first. Check the :ref:`windows-easy-install` section for more
|
||||||
information about how to do that. Once you have it installed, run the same
|
information about how to do that. Once you have it installed, run the same
|
||||||
commands as above, but without the :command:`sudo` prefix.
|
commands as above, but without the ``sudo`` prefix.
|
||||||
|
|
||||||
Once you have virtualenv installed, just fire up a shell and create
|
Once you have virtualenv installed, just fire up a shell and create
|
||||||
your own environment. I usually create a project folder and a :file:`venv`
|
your own environment. I usually create a project folder and a :file:`venv`
|
||||||
|
|
@ -76,7 +72,7 @@ corresponding environment. On OS X and Linux, do the following::
|
||||||
|
|
||||||
If you are a Windows user, the following command is for you::
|
If you are a Windows user, the following command is for you::
|
||||||
|
|
||||||
$ venv\scripts\activate
|
$ venv\Scripts\activate
|
||||||
|
|
||||||
Either way, you should now be using your virtualenv (notice how the prompt of
|
Either way, you should now be using your virtualenv (notice how the prompt of
|
||||||
your shell has changed to show the active environment).
|
your shell has changed to show the active environment).
|
||||||
|
|
@ -99,24 +95,24 @@ System-Wide Installation
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
This is possible as well, though I do not recommend it. Just run
|
This is possible as well, though I do not recommend it. Just run
|
||||||
:command:`pip` with root privileges::
|
``pip`` with root privileges::
|
||||||
|
|
||||||
$ sudo pip install Flask
|
$ sudo pip install Flask
|
||||||
|
|
||||||
(On Windows systems, run it in a command-prompt window with administrator
|
(On Windows systems, run it in a command-prompt window with administrator
|
||||||
privileges, and leave out :command:`sudo`.)
|
privileges, and leave out ``sudo``.)
|
||||||
|
|
||||||
|
|
||||||
Living on the Edge
|
Living on the Edge
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If you want to work with the latest version of Flask, there are two ways: you
|
If you want to work with the latest version of Flask, there are two ways: you
|
||||||
can either let :command:`pip` pull in the development version, or you can tell
|
can either let ``pip`` pull in the development version, or you can tell
|
||||||
it to operate on a git checkout. Either way, virtualenv is recommended.
|
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::
|
Get the git checkout in a new virtualenv and run in development mode::
|
||||||
|
|
||||||
$ git clone http://github.com/pallets/flask.git
|
$ git clone https://github.com/pallets/flask.git
|
||||||
Initialized empty Git repository in ~/dev/flask/.git/
|
Initialized empty Git repository in ~/dev/flask/.git/
|
||||||
$ cd flask
|
$ cd flask
|
||||||
$ virtualenv venv
|
$ virtualenv venv
|
||||||
|
|
@ -131,40 +127,34 @@ This will pull in the dependencies and activate the git head as the current
|
||||||
version inside the virtualenv. Then all you have to do is run ``git pull
|
version inside the virtualenv. Then all you have to do is run ``git pull
|
||||||
origin`` to update to the latest version.
|
origin`` to update to the latest version.
|
||||||
|
|
||||||
|
|
||||||
.. _windows-easy-install:
|
.. _windows-easy-install:
|
||||||
|
|
||||||
`pip` and `setuptools` on Windows
|
`pip` and `setuptools` on Windows
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Sometimes getting the standard "Python packaging tools" like *pip*, *setuptools*
|
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools``
|
||||||
and *virtualenv* can be a little trickier, but nothing very hard. The two crucial
|
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial
|
||||||
packages you will need are setuptools and pip - these will let you install
|
package you will need is pip - this will let you install
|
||||||
anything else (like virtualenv). Fortunately there are two "bootstrap scripts"
|
anything else (like virtualenv). Fortunately there is a "bootstrap script"
|
||||||
you can run to install either.
|
you can run to install.
|
||||||
|
|
||||||
If you don't currently have either, then :file:`get-pip.py` will install both for you
|
If you don't currently have ``pip``, then `get-pip.py` will install it for you.
|
||||||
(you won't need to run :file:`ez_setup.py`).
|
|
||||||
|
|
||||||
`get-pip.py`_
|
`get-pip.py`_
|
||||||
|
|
||||||
To install the latest setuptools, you can use its bootstrap file:
|
It should be double-clickable once you download it. If you already have ``pip``,
|
||||||
|
|
||||||
`ez_setup.py`_
|
|
||||||
|
|
||||||
Either should be double-clickable once you download them. If you already have pip,
|
|
||||||
you can upgrade them by running::
|
you can upgrade them by running::
|
||||||
|
|
||||||
> pip install --upgrade pip setuptools
|
> pip install --upgrade pip setuptools
|
||||||
|
|
||||||
Most often, once you pull up a command prompt you want to be able to type :command:`pip`
|
Most often, once you pull up a command prompt you want to be able to type ``pip``
|
||||||
and :command:`python` which will run those things, but this might not automatically happen
|
and ``python`` which will run those things, but this might not automatically happen
|
||||||
on Windows, because it doesn't know where those executables are (give either a try!).
|
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
|
To fix this, you should be able to navigate to your Python install directory
|
||||||
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
|
(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
|
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
|
||||||
check that you can now just type :command:`python` to bring up the interpreter.
|
check that you can now just type ``python`` to bring up the interpreter.
|
||||||
|
|
||||||
Finally, to install `virtualenv`_, you can simply run::
|
Finally, to install `virtualenv`_, you can simply run::
|
||||||
|
|
||||||
|
|
@ -173,4 +163,3 @@ Finally, to install `virtualenv`_, you can simply run::
|
||||||
Then you can be off on your way following the installation instructions above.
|
Then you can be off on your way following the installation instructions above.
|
||||||
|
|
||||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
||||||
.. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Application Factories
|
||||||
If you are already using packages and blueprints for your application
|
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):
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ This is all that is necessary to properly integrate Celery with Flask::
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
def make_celery(app):
|
def make_celery(app):
|
||||||
celery = Celery(app.import_name, backend=app.config['CELERY_BACKEND'],
|
celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],
|
||||||
broker=app.config['CELERY_BROKER_URL'])
|
broker=app.config['CELERY_BROKER_URL'])
|
||||||
celery.conf.update(app.config)
|
celery.conf.update(app.config)
|
||||||
TaskBase = celery.Task
|
TaskBase = celery.Task
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -47,7 +47,7 @@ the file and redirects the user to the URL for the uploaded file::
|
||||||
|
|
||||||
def allowed_file(filename):
|
def allowed_file(filename):
|
||||||
return '.' in filename and \
|
return '.' in filename and \
|
||||||
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
|
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||||
|
|
||||||
@app.route('/', methods=['GET', 'POST'])
|
@app.route('/', methods=['GET', 'POST'])
|
||||||
def upload_file():
|
def upload_file():
|
||||||
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,19 @@ module. That is quite simple. Imagine a small application looks like
|
||||||
this::
|
this::
|
||||||
|
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/yourapplication.py
|
yourapplication.py
|
||||||
/static
|
/static
|
||||||
/style.css
|
style.css
|
||||||
/templates
|
/templates
|
||||||
layout.html
|
layout.html
|
||||||
index.html
|
index.html
|
||||||
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
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
@ -29,9 +33,9 @@ You should then end up with something like that::
|
||||||
|
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/__init__.py
|
__init__.py
|
||||||
/static
|
/static
|
||||||
/style.css
|
style.css
|
||||||
/templates
|
/templates
|
||||||
layout.html
|
layout.html
|
||||||
index.html
|
index.html
|
||||||
|
|
@ -41,11 +45,36 @@ You should then end up with something like that::
|
||||||
But how do you run your application now? The naive ``python
|
But how do you run your application now? The naive ``python
|
||||||
yourapplication/__init__.py`` will not work. Let's just say that Python
|
yourapplication/__init__.py`` will not work. Let's just say that Python
|
||||||
does not want modules in packages to be the startup file. But that is not
|
does not want modules in packages to be the startup file. But that is not
|
||||||
a big problem, just add a new file called :file:`runserver.py` next to the inner
|
a big problem, just add a new file called :file:`setup.py` next to the inner
|
||||||
:file:`yourapplication` folder with the following contents::
|
:file:`yourapplication` folder with the following contents::
|
||||||
|
|
||||||
from yourapplication import app
|
from setuptools import setup
|
||||||
app.run(debug=True)
|
|
||||||
|
setup(
|
||||||
|
name='yourapplication',
|
||||||
|
packages=['yourapplication'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
In order to run the application you need to export an environment variable
|
||||||
|
that tells Flask where to find the application instance::
|
||||||
|
|
||||||
|
export FLASK_APP=yourapplication
|
||||||
|
|
||||||
|
If you are outside of the project directory make sure to provide the exact
|
||||||
|
path to your application directory. Similarly you can turn on "debug
|
||||||
|
mode" with this environment variable::
|
||||||
|
|
||||||
|
export FLASK_DEBUG=true
|
||||||
|
|
||||||
|
In order to install and run the application you need to issue the following
|
||||||
|
commands::
|
||||||
|
|
||||||
|
pip install -e .
|
||||||
|
flask run
|
||||||
|
|
||||||
What did we gain from this? Now we can restructure the application a bit
|
What did we gain from this? Now we can restructure the application a bit
|
||||||
into multiple modules. The only thing you have to remember is the
|
into multiple modules. The only thing you have to remember is the
|
||||||
|
|
@ -77,12 +106,12 @@ And this is what :file:`views.py` would look like::
|
||||||
You should then end up with something like that::
|
You should then end up with something like that::
|
||||||
|
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/runserver.py
|
setup.py
|
||||||
/yourapplication
|
/yourapplication
|
||||||
/__init__.py
|
__init__.py
|
||||||
/views.py
|
views.py
|
||||||
/static
|
/static
|
||||||
/style.css
|
style.css
|
||||||
/templates
|
/templates
|
||||||
layout.html
|
layout.html
|
||||||
index.html
|
index.html
|
||||||
|
|
@ -105,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/>`_.
|
||||||
|
|
|
||||||
|
|
@ -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,10 +102,10 @@ docs to see the alternative method for running a server.
|
||||||
Invalid Import Name
|
Invalid Import Name
|
||||||
```````````````````
|
```````````````````
|
||||||
|
|
||||||
The ``-a`` argument to :command:`flask` is the name of the module to
|
The ``FLASK_APP`` environment variable is the name of the module to import at
|
||||||
import. In case that module is incorrectly named you will get an import
|
:command:`flask run`. In case that module is incorrectly named you will get an
|
||||||
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
|
||||||
``app`` object.
|
``app`` object.
|
||||||
|
|
@ -264,10 +264,10 @@ some examples::
|
||||||
... def profile(username): pass
|
... def profile(username): pass
|
||||||
...
|
...
|
||||||
>>> with app.test_request_context():
|
>>> with app.test_request_context():
|
||||||
... print url_for('index')
|
... print(url_for('index'))
|
||||||
... print url_for('login')
|
... print(url_for('login'))
|
||||||
... print url_for('login', next='/')
|
... print(url_for('login', next='/'))
|
||||||
... print url_for('profile', username='John Doe')
|
... print(url_for('profile', username='John Doe'))
|
||||||
...
|
...
|
||||||
/
|
/
|
||||||
/login
|
/login
|
||||||
|
|
@ -306,9 +306,9 @@ can be changed by providing the ``methods`` argument to the
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
do_the_login()
|
return do_the_login()
|
||||||
else:
|
else:
|
||||||
show_the_login_form()
|
return show_the_login_form()
|
||||||
|
|
||||||
If ``GET`` is present, ``HEAD`` will be added automatically for you. You
|
If ``GET`` is present, ``HEAD`` will be added automatically for you. You
|
||||||
don't have to deal with that. It will also make sure that ``HEAD`` requests
|
don't have to deal with that. It will also make sure that ``HEAD`` requests
|
||||||
|
|
@ -367,7 +367,7 @@ HTTP has become quite popular lately and browsers are no longer the only
|
||||||
clients that are using HTTP. For instance, many revision control systems
|
clients that are using HTTP. For instance, many revision control systems
|
||||||
use it.
|
use it.
|
||||||
|
|
||||||
.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt
|
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt
|
||||||
|
|
||||||
Static Files
|
Static Files
|
||||||
------------
|
------------
|
||||||
|
|
@ -538,16 +538,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 +570,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 +579,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
|
||||||
|
|
@ -817,6 +817,9 @@ values do not persist across requests, cookies are indeed enabled, and you are
|
||||||
not getting a clear error message, check the size of the cookie in your page
|
not getting a clear error message, check the size of the cookie in your page
|
||||||
responses compared to the size supported by web browsers.
|
responses compared to the size supported by web browsers.
|
||||||
|
|
||||||
|
Besides the default client-side based sessions, if you want to handle
|
||||||
|
sessions on the server-side instead, there are several
|
||||||
|
Flask extensions that support this.
|
||||||
|
|
||||||
Message Flashing
|
Message Flashing
|
||||||
----------------
|
----------------
|
||||||
|
|
|
||||||
|
|
@ -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::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -152,13 +154,13 @@ invalid credentials. Add this new test to the class::
|
||||||
|
|
||||||
def test_login_logout(self):
|
def test_login_logout(self):
|
||||||
rv = self.login('admin', 'default')
|
rv = self.login('admin', 'default')
|
||||||
assert 'You were logged in' in rv.data
|
assert b'You were logged in' in rv.data
|
||||||
rv = self.logout()
|
rv = self.logout()
|
||||||
assert 'You were logged out' in rv.data
|
assert b'You were logged out' in rv.data
|
||||||
rv = self.login('adminx', 'default')
|
rv = self.login('adminx', 'default')
|
||||||
assert 'Invalid username' in rv.data
|
assert b'Invalid username' in rv.data
|
||||||
rv = self.login('admin', 'defaultx')
|
rv = self.login('admin', 'defaultx')
|
||||||
assert 'Invalid password' in rv.data
|
assert b'Invalid password' in rv.data
|
||||||
|
|
||||||
Test Adding Messages
|
Test Adding Messages
|
||||||
--------------------
|
--------------------
|
||||||
|
|
@ -172,9 +174,9 @@ like this::
|
||||||
title='<Hello>',
|
title='<Hello>',
|
||||||
text='<strong>HTML</strong> allowed here'
|
text='<strong>HTML</strong> allowed here'
|
||||||
), follow_redirects=True)
|
), follow_redirects=True)
|
||||||
assert 'No entries here so far' not in rv.data
|
assert b'No entries here so far' not in rv.data
|
||||||
assert '<Hello>' in rv.data
|
assert b'<Hello>' in rv.data
|
||||||
assert '<strong>HTML</strong> allowed here' in rv.data
|
assert b'<strong>HTML</strong> allowed here' in rv.data
|
||||||
|
|
||||||
Here we check that HTML is allowed in the text but not in the title,
|
Here we check that HTML is allowed in the text but not in the title,
|
||||||
which is the intended behavior.
|
which is the intended behavior.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
.. _tutorial-css:
|
.. _tutorial-css:
|
||||||
|
|
||||||
Step 7: Adding Style
|
Step 8: Adding Style
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Now that everything else works, it's time to add some style to the
|
Now that everything else works, it's time to add some style to the
|
||||||
application. Just create a stylesheet called :file:`style.css` in the
|
application. Just create a stylesheet called :file:`style.css` in the
|
||||||
:file:`static` folder we created before:
|
:file:`static` folder:
|
||||||
|
|
||||||
.. sourcecode:: css
|
.. sourcecode:: css
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,23 @@
|
||||||
.. _tutorial-dbcon:
|
.. _tutorial-dbcon:
|
||||||
|
|
||||||
Step 3: Database Connections
|
Step 4: Database Connections
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
We have created a function for establishing a database connection with
|
You currently have a function for establishing a database connection with
|
||||||
`connect_db`, but by itself, that's not particularly useful. Creating and
|
`connect_db`, but by itself, it is not particularly useful. Creating and
|
||||||
closing database connections all the time is very inefficient, so we want
|
closing database connections all the time is very inefficient, so you will
|
||||||
to keep it around for longer. Because database connections encapsulate a
|
need to keep it around for longer. Because database connections
|
||||||
transaction, we also need to make sure that only one request at the time
|
encapsulate a transaction, you will need to make sure that only one
|
||||||
uses the connection. How can we elegantly do that with Flask?
|
request at a time uses the connection. An elegant way to do this is by
|
||||||
|
utilizing the *application context*.
|
||||||
|
|
||||||
This is where the application context comes into play, so let's start
|
Flask provides two contexts: the *application context* and the
|
||||||
there.
|
*request context*. For the time being, all you have to know is that there
|
||||||
|
|
||||||
Flask provides us with two contexts: the application context and the
|
|
||||||
request context. For the time being, all you have to know is that there
|
|
||||||
are special variables that use these. For instance, the
|
are special variables that use these. For instance, the
|
||||||
:data:`~flask.request` variable is the request object associated with
|
:data:`~flask.request` variable is the request object associated with
|
||||||
the current request, whereas :data:`~flask.g` is a general purpose
|
the current request, whereas :data:`~flask.g` is a general purpose
|
||||||
variable associated with the current application context. We will go into
|
variable associated with the current application context. The tutorial
|
||||||
the details of this a bit later.
|
will cover some more details of this later on.
|
||||||
|
|
||||||
For the time being, all you have to know is that you can store information
|
For the time being, all you have to know is that you can store information
|
||||||
safely on the :data:`~flask.g` object.
|
safely on the :data:`~flask.g` object.
|
||||||
|
|
@ -37,7 +35,7 @@ already established connection::
|
||||||
g.sqlite_db = connect_db()
|
g.sqlite_db = connect_db()
|
||||||
return g.sqlite_db
|
return g.sqlite_db
|
||||||
|
|
||||||
So now we know how to connect, but how do we properly disconnect? For
|
Now you know how to connect, but how can you properly disconnect? For
|
||||||
that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
|
that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext`
|
||||||
decorator. It's executed every time the application context tears down::
|
decorator. It's executed every time the application context tears down::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
.. _tutorial-dbinit:
|
.. _tutorial-dbinit:
|
||||||
|
|
||||||
Step 4: Creating The Database
|
Step 5: Creating The Database
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
As outlined earlier, Flaskr is a database powered application, and more
|
As outlined earlier, Flaskr is a database powered application, and more
|
||||||
|
|
@ -16,13 +16,14 @@ Such a schema can be created by piping the ``schema.sql`` file into the
|
||||||
|
|
||||||
The downside of this is that it requires the ``sqlite3`` command to be
|
The downside of this is that it requires the ``sqlite3`` command to be
|
||||||
installed, which is not necessarily the case on every system. This also
|
installed, which is not necessarily the case on every system. This also
|
||||||
requires that we provide the path to the database, which can introduce
|
requires that you provide the path to the database, which can introduce
|
||||||
errors. It's a good idea to add a function that initializes the database
|
errors. It's a good idea to add a function that initializes the database
|
||||||
for you to the application.
|
for you, to the application.
|
||||||
|
|
||||||
To do this, we can create a function and hook it into the :command:`flask`
|
To do this, you can create a function and hook it into a :command:`flask`
|
||||||
command that initializes the database. Let me show you the code first. Just
|
command that initializes the database. For now just take a look at the
|
||||||
add this function below the `connect_db` function in :file:`flaskr.py`::
|
code segment below. A good place to add this function, and command, is
|
||||||
|
just below the `connect_db` function in :file:`flaskr.py`::
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
|
@ -34,27 +35,27 @@ add this function below the `connect_db` function in :file:`flaskr.py`::
|
||||||
def initdb_command():
|
def initdb_command():
|
||||||
"""Initializes the database."""
|
"""Initializes the database."""
|
||||||
init_db()
|
init_db()
|
||||||
print 'Initialized the database.'
|
print('Initialized the database.')
|
||||||
|
|
||||||
The ``app.cli.command()`` decorator registers a new command with the
|
The ``app.cli.command()`` decorator registers a new command with the
|
||||||
:command:`flask` script. When the command executes, Flask will automatically
|
:command:`flask` script. When the command executes, Flask will automatically
|
||||||
create an application context for us bound to the right application.
|
create an application context which is bound to the right application.
|
||||||
Within the function, we can then access :attr:`flask.g` and other things as
|
Within the function, you can then access :attr:`flask.g` and other things as
|
||||||
we would expect. When the script ends, the application context tears down
|
you might expect. When the script ends, the application context tears down
|
||||||
and the database connection is released.
|
and the database connection is released.
|
||||||
|
|
||||||
We want to keep an actual function around that initializes the database,
|
You will want to keep an actual function around that initializes the database,
|
||||||
though, so that we can easily create databases in unit tests later on. (For
|
though, so that we can easily create databases in unit tests later on. (For
|
||||||
more information see :ref:`testing`.)
|
more information see :ref:`testing`.)
|
||||||
|
|
||||||
The :func:`~flask.Flask.open_resource` method of the application object
|
The :func:`~flask.Flask.open_resource` method of the application object
|
||||||
is a convenient helper function that will open a resource that the
|
is a convenient helper function that will open a resource that the
|
||||||
application provides. This function opens a file from the resource
|
application provides. This function opens a file from the resource
|
||||||
location (your ``flaskr`` folder) and allows you to read from it. We are
|
location (the :file:`flaskr/flaskr` folder) and allows you to read from it.
|
||||||
using this here to execute a script on the database connection.
|
It is used in this example to execute a script on the database connection.
|
||||||
|
|
||||||
The connection object provided by SQLite can give us a cursor object.
|
The connection object provided by SQLite can give you a cursor object.
|
||||||
On that cursor, there is a method to execute a complete script. Finally, we
|
On that cursor, there is a method to execute a complete script. Finally, you
|
||||||
only have to commit the changes. SQLite3 and other transactional
|
only have to commit the changes. SQLite3 and other transactional
|
||||||
databases will not commit unless you explicitly tell it to.
|
databases will not commit unless you explicitly tell it to.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,25 @@
|
||||||
Step 0: Creating The Folders
|
Step 0: Creating The Folders
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Before we get started, let's create the folders needed for this
|
Before getting started, you will need to create the folders needed for this
|
||||||
application::
|
application::
|
||||||
|
|
||||||
/flaskr
|
/flaskr
|
||||||
/static
|
/flaskr
|
||||||
/templates
|
/static
|
||||||
|
/templates
|
||||||
|
|
||||||
The ``flaskr`` folder is not a Python package, but just something where we
|
The application will be installed and run as Python package. This is the
|
||||||
drop our files. Later on, we will put our database schema as well as main
|
recommended way to install and run Flask applications. You will see exactly
|
||||||
module into this folder. It is done in the following way. The files inside
|
how to run ``flaskr`` later on in this tutorial. For now go ahead and create
|
||||||
the :file:`static` folder are available to users of the application via HTTP.
|
the applications directory structure. In the next few steps you will be
|
||||||
This is the place where CSS and Javascript files go. Inside the
|
creating the database schema as well as the main module.
|
||||||
:file:`templates` folder, Flask will look for `Jinja2`_ templates. The
|
|
||||||
templates you create later on in the tutorial will go in this directory.
|
|
||||||
|
|
||||||
Continue with :ref:`tutorial-schema`.
|
As a quick side note, the files inside of the :file:`static` folder are
|
||||||
|
available to users of the application via HTTP. This is the place where CSS and
|
||||||
|
JavaScript files go. Inside the :file:`templates` folder, Flask will look for
|
||||||
|
`Jinja2`_ templates. You will see examples of this later on.
|
||||||
|
|
||||||
|
For now you should continue with :ref:`tutorial-schema`.
|
||||||
|
|
||||||
.. _Jinja2: http://jinja.pocoo.org/
|
.. _Jinja2: http://jinja.pocoo.org/
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ the `example source`_.
|
||||||
folders
|
folders
|
||||||
schema
|
schema
|
||||||
setup
|
setup
|
||||||
|
packaging
|
||||||
dbcon
|
dbcon
|
||||||
dbinit
|
dbinit
|
||||||
views
|
views
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
Introducing Flaskr
|
Introducing Flaskr
|
||||||
==================
|
==================
|
||||||
|
|
||||||
We will call our blogging application Flaskr, but feel free to choose your own
|
This tutorial will demonstrate a blogging application named Flaskr, but feel
|
||||||
less Web-2.0-ish name ;) Essentially, we want it to do the following things:
|
free to choose your own less Web-2.0-ish name ;) Essentially, it will do the
|
||||||
|
following things:
|
||||||
|
|
||||||
1. Let the user sign in and out with credentials specified in the
|
1. Let the user sign in and out with credentials specified in the
|
||||||
configuration. Only one user is supported.
|
configuration. Only one user is supported.
|
||||||
|
|
@ -14,8 +15,8 @@ less Web-2.0-ish name ;) Essentially, we want it to do the following things:
|
||||||
3. The index page shows all entries so far in reverse chronological order
|
3. The index page shows all entries so far in reverse chronological order
|
||||||
(newest on top) and the user can add new ones from there if logged in.
|
(newest on top) and the user can add new ones from there if logged in.
|
||||||
|
|
||||||
We will be using SQLite3 directly for this application because it's good
|
SQLite3 will be used directly for this application because it's good enough
|
||||||
enough for an application of this size. For larger applications, however,
|
for an application of this size. For larger applications, however,
|
||||||
it makes a lot of sense to use `SQLAlchemy`_, as it handles database
|
it makes a lot of sense to use `SQLAlchemy`_, as it handles database
|
||||||
connections in a more intelligent way, allowing you to target different
|
connections in a more intelligent way, allowing you to target different
|
||||||
relational databases at once and more. You might also want to consider
|
relational databases at once and more. You might also want to consider
|
||||||
|
|
@ -30,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/
|
||||||
|
|
|
||||||
108
docs/tutorial/packaging.rst
Normal file
108
docs/tutorial/packaging.rst
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
.. _tutorial-packaging:
|
||||||
|
|
||||||
|
Step 3: Installing flaskr as a Package
|
||||||
|
======================================
|
||||||
|
|
||||||
|
Flask is now shipped with built-in support for `Click`_. Click provides
|
||||||
|
Flask with enhanced and extensible command line utilities. Later in this
|
||||||
|
tutorial you will see exactly how to extend the ``flask`` command line
|
||||||
|
interface (CLI).
|
||||||
|
|
||||||
|
A useful pattern to manage a Flask application is to install your app
|
||||||
|
following the `Python Packaging Guide`_. Presently this involves
|
||||||
|
creating two new files; :file:`setup.py` and :file:`MANIFEST.in` in the
|
||||||
|
projects root directory. You also need to add an :file:`__init__.py`
|
||||||
|
file to make the :file:`flaskr/flaskr` directory a package. After these
|
||||||
|
changes, your code structure should be::
|
||||||
|
|
||||||
|
/flaskr
|
||||||
|
/flaskr
|
||||||
|
__init__.py
|
||||||
|
/static
|
||||||
|
/templates
|
||||||
|
flaskr.py
|
||||||
|
schema.sql
|
||||||
|
setup.py
|
||||||
|
MANIFEST.in
|
||||||
|
|
||||||
|
The content of the ``setup.py`` file for ``flaskr`` is:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='flaskr',
|
||||||
|
packages=['flaskr'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
When using setuptools, it is also necessary to specify any special files
|
||||||
|
that should be included in your package (in the :file:`MANIFEST.in`).
|
||||||
|
In this case, the static and templates directories need to be included,
|
||||||
|
as well as the schema. Create the :file:`MANIFEST.in` and add the
|
||||||
|
following lines::
|
||||||
|
|
||||||
|
graft flaskr/templates
|
||||||
|
graft flaskr/static
|
||||||
|
include flaskr/schema.sql
|
||||||
|
|
||||||
|
To simplify locating the application, add the following import statement
|
||||||
|
into this file, :file:`flaskr/__init__.py`:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
from .flaskr import app
|
||||||
|
|
||||||
|
This import statement brings the application instance into the top-level
|
||||||
|
of the application package. When it is time to run the application, the
|
||||||
|
Flask development server needs the location of the app instance. This
|
||||||
|
import statement simplifies the location process. Without it the export
|
||||||
|
statement a few steps below would need to be
|
||||||
|
``export FLASK_APP=flaskr.flaskr``.
|
||||||
|
|
||||||
|
At this point you should be able to install the application. As usual, it
|
||||||
|
is recommended to install your Flask application within a `virtualenv`_.
|
||||||
|
With that said, go ahead and install the application with::
|
||||||
|
|
||||||
|
pip install --editable .
|
||||||
|
|
||||||
|
The above installation command assumes that it is run within the projects
|
||||||
|
root directory, `flaskr/`. The `editable` flag allows editing
|
||||||
|
source code without having to reinstall the Flask app each time you make
|
||||||
|
changes. The flaskr app is now installed in your virtualenv (see output
|
||||||
|
of ``pip freeze``).
|
||||||
|
|
||||||
|
With that out of the way, you should be able to start up the application.
|
||||||
|
Do this with the following commands::
|
||||||
|
|
||||||
|
export FLASK_APP=flaskr
|
||||||
|
export FLASK_DEBUG=true
|
||||||
|
flask run
|
||||||
|
|
||||||
|
(In case you are on Windows you need to use `set` instead of `export`).
|
||||||
|
The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger.
|
||||||
|
*Never leave debug mode activated in a production system*, because it will
|
||||||
|
allow users to execute code on the server!
|
||||||
|
|
||||||
|
You will see a message telling you that server has started along with
|
||||||
|
the address at which you can access it.
|
||||||
|
|
||||||
|
When you head over to the server in your browser, you will get a 404 error
|
||||||
|
because we don't have any views yet. That will be addressed a little later,
|
||||||
|
but first, you should get the database working.
|
||||||
|
|
||||||
|
.. admonition:: Externally Visible Server
|
||||||
|
|
||||||
|
Want your server to be publicly available? Check out the
|
||||||
|
:ref:`externally visible server <public-server>` section for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
Continue with :ref:`tutorial-dbcon`.
|
||||||
|
|
||||||
|
.. _Click: http://click.pocoo.org
|
||||||
|
.. _Python Packaging Guide: https://packaging.python.org
|
||||||
|
.. _virtualenv: https://virtualenv.pypa.io
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
Step 1: Database Schema
|
Step 1: Database Schema
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
First, we want to create the database schema. Only a single table is needed
|
In this step, you will create the database schema. Only a single table is
|
||||||
for this application and we only want to support SQLite, so creating the
|
needed for this application and it will only support SQLite. All you need to do
|
||||||
database schema is quite easy. Just put the following contents into a file
|
is put the following contents into a file named :file:`schema.sql` in the
|
||||||
named `schema.sql` in the just created `flaskr` folder:
|
:file:`flaskr/flaskr` folder:
|
||||||
|
|
||||||
.. sourcecode:: sql
|
.. sourcecode:: sql
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ named `schema.sql` in the just created `flaskr` folder:
|
||||||
'text' text not null
|
'text' text not null
|
||||||
);
|
);
|
||||||
|
|
||||||
This schema consists of a single table called ``entries``. Each row in
|
This schema consists of a single table called ``entries``. Each row in
|
||||||
this table has an ``id``, a ``title``, and a ``text``. The ``id`` is an
|
this table has an ``id``, a ``title``, and a ``text``. The ``id`` is an
|
||||||
automatically incrementing integer and a primary key, the other two are
|
automatically incrementing integer and a primary key, the other two are
|
||||||
strings that must not be null.
|
strings that must not be null.
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@
|
||||||
Step 2: Application Setup Code
|
Step 2: Application Setup Code
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Now that we have the schema in place, we can create the application module.
|
Now that the schema is in place, you can create the application module,
|
||||||
Let's call it ``flaskr.py``. We will place this file inside the ``flaskr``
|
:file:`flaskr.py`. This file should be placed inside of the
|
||||||
folder. We will begin by adding the imports we need and by adding the config
|
:file:`flaskr/flaskr` folder. The first several lines of code in the
|
||||||
section. For small applications, it is possible to drop the configuration
|
application module are the needed import statements. After that there will be a
|
||||||
directly into the module, and this is what we will be doing here. However,
|
few lines of configuration code. For small applications like ``flaskr``, it is
|
||||||
a cleaner solution would be to create a separate ``.ini`` or ``.py`` file,
|
possible to drop the configuration directly into the module. However, a cleaner
|
||||||
load that, and import the values from there.
|
solution is to create a separate ``.ini`` or ``.py`` file, load that, and
|
||||||
|
import the values from there.
|
||||||
|
|
||||||
First, we add the imports in :file:`flaskr.py`::
|
Here are the import statements (in :file:`flaskr.py`)::
|
||||||
|
|
||||||
# all the imports
|
# all the imports
|
||||||
import os
|
import os
|
||||||
|
|
@ -19,12 +20,13 @@ First, we add the imports in :file:`flaskr.py`::
|
||||||
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
from flask import Flask, request, session, g, redirect, url_for, abort, \
|
||||||
render_template, flash
|
render_template, flash
|
||||||
|
|
||||||
Next, we can create our actual application and initialize it with the
|
The next couple lines will create the actual application instance and
|
||||||
config from the same file in :file:`flaskr.py`::
|
initialize it with the config from the same file in :file:`flaskr.py`:
|
||||||
|
|
||||||
# create our little application :)
|
.. sourcecode:: python
|
||||||
app = Flask(__name__)
|
|
||||||
app.config.from_object(__name__)
|
app = Flask(__name__) # create the application instance :)
|
||||||
|
app.config.from_object(__name__) # load config from this file , flaskr.py
|
||||||
|
|
||||||
# Load default config and override config from an environment variable
|
# Load default config and override config from an environment variable
|
||||||
app.config.update(dict(
|
app.config.update(dict(
|
||||||
|
|
@ -35,8 +37,8 @@ config from the same file in :file:`flaskr.py`::
|
||||||
))
|
))
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
The :class:`~flask.Config` object works similarly to a dictionary so we
|
The :class:`~flask.Config` object works similarly to a dictionary, so it can be
|
||||||
can update it with new values.
|
updated with new values.
|
||||||
|
|
||||||
.. admonition:: Database Path
|
.. admonition:: Database Path
|
||||||
|
|
||||||
|
|
@ -55,10 +57,10 @@ can update it with new values.
|
||||||
|
|
||||||
Usually, it is a good idea to load a separate, environment-specific
|
Usually, it is a good idea to load a separate, environment-specific
|
||||||
configuration file. Flask allows you to import multiple configurations and it
|
configuration file. Flask allows you to import multiple configurations and it
|
||||||
will use the setting defined in the last import. This enables robust
|
will use the setting defined in the last import. This enables robust
|
||||||
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this.
|
configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this.
|
||||||
|
|
||||||
.. code-block:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||||
|
|
||||||
|
|
@ -74,15 +76,15 @@ that in all cases, only variable names that are uppercase are considered.
|
||||||
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
The ``SECRET_KEY`` is needed to keep the client-side sessions secure.
|
||||||
Choose that key wisely and as hard to guess and complex as possible.
|
Choose that key wisely and as hard to guess and complex as possible.
|
||||||
|
|
||||||
We will also add a method that allows for easy connections to the
|
Lastly, you will add a method that allows for easy connections to the
|
||||||
specified database. This can be used to open a connection on request and
|
specified database. This can be used to open a connection on request and
|
||||||
also from the interactive Python shell or a script. This will come in
|
also from the interactive Python shell or a script. This will come in
|
||||||
handy later. We create a simple database connection through SQLite and
|
handy later. You can create a simple database connection through SQLite and
|
||||||
then tell it to use the :class:`sqlite3.Row` object to represent rows.
|
then tell it to use the :class:`sqlite3.Row` object to represent rows.
|
||||||
This allows us to treat the rows as if they were dictionaries instead of
|
This allows the rows to be treated as if they were dictionaries instead of
|
||||||
tuples.
|
tuples.
|
||||||
|
|
||||||
::
|
.. sourcecode:: python
|
||||||
|
|
||||||
def connect_db():
|
def connect_db():
|
||||||
"""Connects to the specific database."""
|
"""Connects to the specific database."""
|
||||||
|
|
@ -90,29 +92,6 @@ tuples.
|
||||||
rv.row_factory = sqlite3.Row
|
rv.row_factory = sqlite3.Row
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
With that out of the way, you should be able to start up the application
|
In the next section you will see how to run the application.
|
||||||
without problems. Do this with the following commands::
|
|
||||||
|
|
||||||
export FLASK_APP=flaskr
|
Continue with :ref:`tutorial-packaging`.
|
||||||
export FLASK_DEBUG=1
|
|
||||||
flask run
|
|
||||||
|
|
||||||
(In case you are on Windows you need to use `set` instead of `export`).
|
|
||||||
The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger.
|
|
||||||
*Never leave debug mode activated in a production system*, because it will
|
|
||||||
allow users to execute code on the server!
|
|
||||||
|
|
||||||
You will see a message telling you that server has started along with
|
|
||||||
the address at which you can access it.
|
|
||||||
|
|
||||||
When you head over to the server in your browser, you will get a 404 error
|
|
||||||
because we don't have any views yet. We will focus on that a little later,
|
|
||||||
but first, we should get the database working.
|
|
||||||
|
|
||||||
.. admonition:: Externally Visible Server
|
|
||||||
|
|
||||||
Want your server to be publicly available? Check out the
|
|
||||||
:ref:`externally visible server <public-server>` section for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
Continue with :ref:`tutorial-dbcon`.
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
.. _tutorial-templates:
|
.. _tutorial-templates:
|
||||||
|
|
||||||
Step 6: The Templates
|
Step 7: The Templates
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Now we should start working on the templates. If we were to request the URLs
|
Now it is time to start working on the templates. As you may have
|
||||||
now, we would only get an exception that Flask cannot find the templates. The
|
noticed, if you make requests with the app running, you will get
|
||||||
templates are using `Jinja2`_ syntax and have autoescaping enabled by
|
an exception that Flask cannot find the templates. The templates
|
||||||
|
are using `Jinja2`_ syntax and have autoescaping enabled by
|
||||||
default. This means that unless you mark a value in the code with
|
default. This means that unless you mark a value in the code with
|
||||||
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
:class:`~flask.Markup` or with the ``|safe`` filter in the template,
|
||||||
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
Jinja2 will ensure that special characters such as ``<`` or ``>`` are
|
||||||
|
|
@ -57,9 +58,9 @@ 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. We also tell the
|
in with the :func:`~flask.render_template` function. Notice that the form is
|
||||||
form to submit to your `add_entry` function and use ``POST`` as HTTP
|
configured to submit to the `add_entry` view function and use ``POST`` as
|
||||||
method:
|
HTTP method:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
|
@ -78,9 +79,9 @@ 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 %}
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,89 @@ expected, it's probably not a bad idea to add automated tests to simplify
|
||||||
modifications in the future. The application above is used as a basic
|
modifications in the future. The application above is used as a basic
|
||||||
example of how to perform unit testing in the :ref:`testing` section of the
|
example of how to perform unit testing in the :ref:`testing` section of the
|
||||||
documentation. Go there to see how easy it is to test Flask applications.
|
documentation. Go there to see how easy it is to test Flask applications.
|
||||||
|
|
||||||
|
Adding tests to flaskr
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Assuming you have seen the :ref:`testing` section and have either written
|
||||||
|
your own tests for ``flaskr`` or have followed along with the examples
|
||||||
|
provided, you might be wondering about ways to organize the project.
|
||||||
|
|
||||||
|
One possible and recommended project structure is::
|
||||||
|
|
||||||
|
flaskr/
|
||||||
|
flaskr/
|
||||||
|
__init__.py
|
||||||
|
static/
|
||||||
|
templates/
|
||||||
|
tests/
|
||||||
|
test_flaskr.py
|
||||||
|
setup.py
|
||||||
|
MANIFEST.in
|
||||||
|
|
||||||
|
For now go ahead a create the :file:`tests/` directory as well as the
|
||||||
|
:file:`test_flaskr.py` file.
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
At this point you can run the tests. Here ``pytest`` will be used.
|
||||||
|
|
||||||
|
.. note:: Make sure that ``pytest`` is installed in the same virtualenv
|
||||||
|
as flaskr. Otherwise ``pytest`` test will not be able to import the
|
||||||
|
required components to test the application::
|
||||||
|
|
||||||
|
pip install -e .
|
||||||
|
pip install pytest
|
||||||
|
|
||||||
|
Run and watch the tests pass, within the top-level :file:`flaskr/`
|
||||||
|
directory as::
|
||||||
|
|
||||||
|
pytest
|
||||||
|
|
||||||
|
Testing + setuptools
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
One way to handle testing is to integrate it with ``setuptools``. Here
|
||||||
|
that requires adding a couple of lines to the :file:`setup.py` file and
|
||||||
|
creating a new file :file:`setup.cfg`. One benefit of running the tests
|
||||||
|
this way is that you do not have to install ``pytest``. Go ahead and
|
||||||
|
update the :file:`setup.py` file to contain::
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='flaskr',
|
||||||
|
packages=['flaskr'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
setup_requires=[
|
||||||
|
'pytest-runner',
|
||||||
|
],
|
||||||
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
Now create :file:`setup.cfg` in the project root (alongside
|
||||||
|
:file:`setup.py`)::
|
||||||
|
|
||||||
|
[aliases]
|
||||||
|
test=pytest
|
||||||
|
|
||||||
|
Now you can run::
|
||||||
|
|
||||||
|
python setup.py test
|
||||||
|
|
||||||
|
This calls on the alias created in :file:`setup.cfg` which in turn runs
|
||||||
|
``pytest`` via ``pytest-runner``, as the :file:`setup.py` script has
|
||||||
|
been called. (Recall the `setup_requires` argument in :file:`setup.py`)
|
||||||
|
Following the standard rules of test-discovery your tests will be
|
||||||
|
found, run, and hopefully pass.
|
||||||
|
|
||||||
|
This is one possible way to run and manage testing. Here ``pytest`` is
|
||||||
|
used, but there are other options such as ``nose``. Integrating testing
|
||||||
|
with ``setuptools`` is convenient because it is not necessary to actually
|
||||||
|
download ``pytest`` or any other testing framework one might use.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
.. _tutorial-views:
|
.. _tutorial-views:
|
||||||
|
|
||||||
Step 5: The View Functions
|
Step 6: The View Functions
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Now that the database connections are working, we can start writing the
|
Now that the database connections are working, you can start writing the
|
||||||
view functions. We will need four of them:
|
view functions. You will need four of them:
|
||||||
|
|
||||||
Show Entries
|
Show Entries
|
||||||
------------
|
------------
|
||||||
|
|
@ -30,7 +30,7 @@ Add New Entry
|
||||||
|
|
||||||
This view lets the user add new entries if they are logged in. This only
|
This view lets the user add new entries if they are logged in. This only
|
||||||
responds to ``POST`` requests; the actual form is shown on the
|
responds to ``POST`` requests; the actual form is shown on the
|
||||||
`show_entries` page. If everything worked out well, we will
|
`show_entries` page. If everything worked out well, it will
|
||||||
:func:`~flask.flash` an information message to the next request and
|
:func:`~flask.flash` an information message to the next request and
|
||||||
redirect back to the `show_entries` page::
|
redirect back to the `show_entries` page::
|
||||||
|
|
||||||
|
|
@ -45,8 +45,8 @@ redirect back to the `show_entries` page::
|
||||||
flash('New entry was successfully posted')
|
flash('New entry was successfully posted')
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('show_entries'))
|
||||||
|
|
||||||
Note that we check that the user is logged in here (the `logged_in` key is
|
Note that this view checks that the user is logged in (that is, if the
|
||||||
present in the session and ``True``).
|
`logged_in` key is present in the session and ``True``).
|
||||||
|
|
||||||
.. admonition:: Security Note
|
.. admonition:: Security Note
|
||||||
|
|
||||||
|
|
@ -81,11 +81,11 @@ notified about that, and the user is asked again::
|
||||||
return render_template('login.html', error=error)
|
return render_template('login.html', error=error)
|
||||||
|
|
||||||
The `logout` function, on the other hand, removes that key from the session
|
The `logout` function, on the other hand, removes that key from the session
|
||||||
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
again. There is a neat trick here: if you use the :meth:`~dict.pop` method
|
||||||
of the dict and pass a second parameter to it (the default), the method
|
of the dict and pass a second parameter to it (the default), the method
|
||||||
will delete the key from the dictionary if present or do nothing when that
|
will delete the key from the dictionary if present or do nothing when that
|
||||||
key is not in there. This is helpful because now we don't have to check
|
key is not in there. This is helpful because now it is not necessary to
|
||||||
if the user was logged in.
|
check if the user was logged in.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,45 @@ providing the ``--upgrade`` parameter::
|
||||||
|
|
||||||
$ pip install --upgrade Flask
|
$ pip install --upgrade Flask
|
||||||
|
|
||||||
.. _upgrading-to-10:
|
.. _upgrading-to-012:
|
||||||
|
|
||||||
|
Version 0.12
|
||||||
|
------------
|
||||||
|
|
||||||
|
Changes to send_file
|
||||||
|
````````````````````
|
||||||
|
|
||||||
|
The ``filename`` is no longer automatically inferred from file-like objects.
|
||||||
|
This means that the following code will no longer automatically have
|
||||||
|
``X-Sendfile`` support, etag generation or MIME-type guessing::
|
||||||
|
|
||||||
|
response = send_file(open('/path/to/file.txt'))
|
||||||
|
|
||||||
|
Any of the following is functionally equivalent::
|
||||||
|
|
||||||
|
fname = '/path/to/file.txt'
|
||||||
|
|
||||||
|
# Just pass the filepath directly
|
||||||
|
response = send_file(fname)
|
||||||
|
|
||||||
|
# Set the MIME-type and ETag explicitly
|
||||||
|
response = send_file(open(fname), mimetype='text/plain')
|
||||||
|
response.set_etag(...)
|
||||||
|
|
||||||
|
# Set `attachment_filename` for MIME-type guessing
|
||||||
|
# ETag still needs to be manually set
|
||||||
|
response = send_file(open(fname), attachment_filename=fname)
|
||||||
|
response.set_etag(...)
|
||||||
|
|
||||||
|
The reason for this is that some file-like objects have an invalid or even
|
||||||
|
misleading ``name`` attribute. Silently swallowing errors in such cases was not
|
||||||
|
a satisfying solution.
|
||||||
|
|
||||||
|
Additionally the default of falling back to ``application/octet-stream`` has
|
||||||
|
been restricted. If Flask can't guess one or the user didn't provide one, the
|
||||||
|
function fails if no filename information was provided.
|
||||||
|
|
||||||
|
.. _upgrading-to-011:
|
||||||
|
|
||||||
Version 0.11
|
Version 0.11
|
||||||
------------
|
------------
|
||||||
|
|
@ -105,7 +143,7 @@ when there is no request context yet but an application context. The old
|
||||||
``flask.Flask.request_globals_class`` attribute was renamed to
|
``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
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -160,7 +198,7 @@ applications with Flask. Because we want to make upgrading as easy as
|
||||||
possible we tried to counter the problems arising from these changes by
|
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
examples/flaskr/.gitignore
vendored
1
examples/flaskr/.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
flaskr.db
|
flaskr.db
|
||||||
|
.eggs/
|
||||||
|
|
|
||||||
3
examples/flaskr/MANIFEST.in
Normal file
3
examples/flaskr/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
graft flaskr/templates
|
||||||
|
graft flaskr/static
|
||||||
|
include flaskr/schema.sql
|
||||||
|
|
@ -13,15 +13,19 @@
|
||||||
export an FLASKR_SETTINGS environment variable
|
export an FLASKR_SETTINGS environment variable
|
||||||
pointing to a configuration file.
|
pointing to a configuration file.
|
||||||
|
|
||||||
2. Instruct flask to use the right application
|
2. install the app from the root of the project directory
|
||||||
|
|
||||||
|
pip install --editable .
|
||||||
|
|
||||||
|
3. Instruct flask to use the right application
|
||||||
|
|
||||||
export FLASK_APP=flaskr
|
export FLASK_APP=flaskr
|
||||||
|
|
||||||
3. initialize the database with this command:
|
4. initialize the database with this command:
|
||||||
|
|
||||||
flask initdb
|
flask initdb
|
||||||
|
|
||||||
4. now you can run flaskr:
|
5. now you can run flaskr:
|
||||||
|
|
||||||
flask run
|
flask run
|
||||||
|
|
||||||
|
|
@ -30,5 +34,5 @@
|
||||||
|
|
||||||
~ Is it tested?
|
~ Is it tested?
|
||||||
|
|
||||||
You betcha. Run the `test_flaskr.py` file to see
|
You betcha. Run `python setup.py test` to see
|
||||||
the tests pass.
|
the tests pass.
|
||||||
|
|
|
||||||
1
examples/flaskr/flaskr/__init__.py
Normal file
1
examples/flaskr/flaskr/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from .flaskr import app
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
{% 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 %}
|
||||||
2
examples/flaskr/setup.cfg
Normal file
2
examples/flaskr/setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[tool:pytest]
|
||||||
|
test=pytest
|
||||||
16
examples/flaskr/setup.py
Normal file
16
examples/flaskr/setup.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='flaskr',
|
||||||
|
packages=['flaskr'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
setup_requires=[
|
||||||
|
'pytest-runner',
|
||||||
|
],
|
||||||
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -9,11 +9,10 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import flaskr
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import pytest
|
||||||
|
from flaskr import flaskr
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
2
examples/minitwit/.gitignore
vendored
Normal file
2
examples/minitwit/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
minitwit.db
|
||||||
|
.eggs/
|
||||||
3
examples/minitwit/MANIFEST.in
Normal file
3
examples/minitwit/MANIFEST.in
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
graft minitwit/templates
|
||||||
|
graft minitwit/static
|
||||||
|
include minitwit/schema.sql
|
||||||
|
|
@ -14,15 +14,19 @@
|
||||||
export an MINITWIT_SETTINGS environment variable
|
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
|
||||||
|
|
||||||
|
|
@ -31,5 +35,5 @@
|
||||||
|
|
||||||
~ Is it tested?
|
~ Is it tested?
|
||||||
|
|
||||||
You betcha. Run the `test_minitwit.py` file to
|
You betcha. Run the `python setup.py test` file to
|
||||||
see the tests pass.
|
see the tests pass.
|
||||||
|
|
|
||||||
1
examples/minitwit/minitwit/__init__.py
Normal file
1
examples/minitwit/minitwit/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from .minitwit import app
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
2
examples/minitwit/setup.cfg
Normal file
2
examples/minitwit/setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[aliases]
|
||||||
|
test=pytest
|
||||||
16
examples/minitwit/setup.py
Normal file
16
examples/minitwit/setup.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='minitwit',
|
||||||
|
packages=['minitwit'],
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
|
setup_requires=[
|
||||||
|
'pytest-runner',
|
||||||
|
],
|
||||||
|
tests_require=[
|
||||||
|
'pytest',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import minitwit
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import pytest
|
import pytest
|
||||||
|
from minitwit import minitwit
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
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!'
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
A simple example for integrating [Persona](https://login.persona.org/) into a
|
|
||||||
Flask application. In addition to Flask, it requires the
|
|
||||||
[requests](www.python-requests.org/) library.
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
from flask import Flask, render_template, session, request, abort, g
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config.update(
|
|
||||||
DEBUG=True,
|
|
||||||
SECRET_KEY='my development key',
|
|
||||||
PERSONA_JS='https://login.persona.org/include.js',
|
|
||||||
PERSONA_VERIFIER='https://verifier.login.persona.org/verify',
|
|
||||||
)
|
|
||||||
app.config.from_envvar('PERSONA_SETTINGS', silent=True)
|
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
|
||||||
def get_current_user():
|
|
||||||
g.user = None
|
|
||||||
email = session.get('email')
|
|
||||||
if email is not None:
|
|
||||||
g.user = email
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
"""Just a generic index page to show."""
|
|
||||||
return render_template('index.html')
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/_auth/login', methods=['GET', 'POST'])
|
|
||||||
def login_handler():
|
|
||||||
"""This is used by the persona.js file to kick off the
|
|
||||||
verification securely from the server side. If all is okay
|
|
||||||
the email address is remembered on the server.
|
|
||||||
"""
|
|
||||||
resp = requests.post(app.config['PERSONA_VERIFIER'], data={
|
|
||||||
'assertion': request.form['assertion'],
|
|
||||||
'audience': request.host_url,
|
|
||||||
}, verify=True)
|
|
||||||
if resp.ok:
|
|
||||||
verification_data = resp.json()
|
|
||||||
if verification_data['status'] == 'okay':
|
|
||||||
session['email'] = verification_data['email']
|
|
||||||
return 'OK'
|
|
||||||
|
|
||||||
abort(400)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/_auth/logout', methods=['POST'])
|
|
||||||
def logout_handler():
|
|
||||||
"""This is what persona.js will call to sign the user
|
|
||||||
out again.
|
|
||||||
"""
|
|
||||||
session.clear()
|
|
||||||
return 'OK'
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
$(function() {
|
|
||||||
/* convert the links into clickable buttons that go to the
|
|
||||||
persona service */
|
|
||||||
$('a.signin').on('click', function() {
|
|
||||||
navigator.id.request({
|
|
||||||
siteName: 'Flask Persona Example'
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('a.signout').on('click', function() {
|
|
||||||
navigator.id.logout();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
/* watch persona state changes */
|
|
||||||
navigator.id.watch({
|
|
||||||
loggedInUser: $CURRENT_USER,
|
|
||||||
onlogin: function(assertion) {
|
|
||||||
/* because the login needs to verify the provided assertion
|
|
||||||
with the persona service which requires an HTTP request,
|
|
||||||
this could take a bit. To not confuse the user we show
|
|
||||||
a progress box */
|
|
||||||
var box = $('<div class=signinprogress></div>')
|
|
||||||
.hide()
|
|
||||||
.text('Please wait ...')
|
|
||||||
.appendTo('body')
|
|
||||||
.fadeIn('fast');
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: $URL_ROOT + '_auth/login',
|
|
||||||
data: {assertion: assertion},
|
|
||||||
success: function(res, status, xhr) { window.location.reload(); },
|
|
||||||
error: function(xhr, status, err) {
|
|
||||||
box.remove();
|
|
||||||
navigator.id.logout();
|
|
||||||
alert('Login failure: ' + err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onlogout: function() {
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: $URL_ROOT + '_auth/logout',
|
|
||||||
success: function(res, status, xhr) { window.location.reload(); },
|
|
||||||
error: function(xhr, status, err) {
|
|
||||||
alert('Logout failure: ' + err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -1,39 +0,0 @@
|
||||||
html {
|
|
||||||
background: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Verdana', sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
margin: 30px auto;
|
|
||||||
width: 720px;
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, a {
|
|
||||||
color: #d00;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.authbar {
|
|
||||||
background: #eee;
|
|
||||||
padding: 0 15px;
|
|
||||||
margin: 10px -15px;
|
|
||||||
line-height: 25px;
|
|
||||||
height: 25px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.signinprogress {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.8) url(spinner.png) center center no-repeat;
|
|
||||||
font-size: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
{% extends "layout.html" %}
|
|
||||||
{% block title %}Welcome{% endblock %}
|
|
||||||
{% block body %}
|
|
||||||
<h2>Welcome</h2>
|
|
||||||
<p>
|
|
||||||
This is a small example application that shows how to integrate
|
|
||||||
Mozilla's persona signin service into a Flask application.
|
|
||||||
<p>
|
|
||||||
The advantage of persona over your own login system is that the
|
|
||||||
password is managed outside of your application and you get
|
|
||||||
a verified mail address as primary identifier for your user.
|
|
||||||
<p>
|
|
||||||
In this example nothing is actually stored on the server, it
|
|
||||||
just takes over the email address from the persona verifier
|
|
||||||
and stores it in a session.
|
|
||||||
{% if g.user %}
|
|
||||||
<p>
|
|
||||||
You are now logged in as <strong>{{ g.user }}</strong>
|
|
||||||
{% else %}
|
|
||||||
<p>
|
|
||||||
To sign in click the sign in button above.
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<title>{% block title %}{% endblock %} | Flask Persona Example</title>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
|
||||||
<script src="{{ config.PERSONA_JS }}"></script>
|
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
|
||||||
<script>
|
|
||||||
/* the url root is useful for doing HTTP requests */
|
|
||||||
var $URL_ROOT = {{ request.url_root|tojson }};
|
|
||||||
|
|
||||||
/* we store the current user here so that the persona
|
|
||||||
javascript support knows about the current user */
|
|
||||||
var $CURRENT_USER = {{ g.user|tojson }};
|
|
||||||
</script>
|
|
||||||
<script src="{{ url_for('static', filename='persona.js') }}"></script>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
||||||
<header>
|
|
||||||
<h1>Mozilla Persona Example</h1>
|
|
||||||
<div class="authbar">
|
|
||||||
{% if g.user %}
|
|
||||||
Signed in as <em>{{ g.user }}</em>
|
|
||||||
(<a href="#" class="signout">Sign out</a>)
|
|
||||||
{% else %}
|
|
||||||
Not signed in. <a href="#" class="signin">Sign in</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
133
flask/app.py
133
flask/app.py
|
|
@ -14,7 +14,6 @@ from threading import Lock
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
from werkzeug.datastructures import ImmutableDict
|
from werkzeug.datastructures import ImmutableDict
|
||||||
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
|
||||||
|
|
@ -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}
|
||||||
|
|
||||||
|
|
@ -519,26 +529,29 @@ class Flask(_PackageBoundObject):
|
||||||
#: def to_python(self, value):
|
#: def to_python(self, value):
|
||||||
#: return value.split(',')
|
#: return value.split(',')
|
||||||
#: def to_url(self, values):
|
#: def to_url(self, values):
|
||||||
#: return ','.join(BaseConverter.to_url(value)
|
#: return ','.join(super(ListConverter, self).to_url(value)
|
||||||
#: for value in values)
|
#: for value in values)
|
||||||
#:
|
#:
|
||||||
#: app = Flask(__name__)
|
#: app = Flask(__name__)
|
||||||
#: 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 +827,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,25 +839,31 @@ 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)
|
||||||
options.setdefault('use_debugger', self.debug)
|
options.setdefault('use_debugger', self.debug)
|
||||||
options.setdefault('passthrough_errors', True)
|
|
||||||
try:
|
try:
|
||||||
run_simple(host, port, self, **options)
|
run_simple(host, port, self, **options)
|
||||||
finally:
|
finally:
|
||||||
# reset the first request information if the development server
|
# reset the first request information if the development server
|
||||||
# resetted normally. This makes it possible to restart the server
|
# reset normally. This makes it possible to restart the server
|
||||||
# without reloader and that stuff from an interactive shell.
|
# without reloader and that stuff from an interactive shell.
|
||||||
self._got_first_request = False
|
self._got_first_request = False
|
||||||
|
|
||||||
|
|
@ -877,9 +897,9 @@ class Flask(_PackageBoundObject):
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
|
|
||||||
class CustomClient(FlaskClient):
|
class CustomClient(FlaskClient):
|
||||||
def __init__(self, authentication=None, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
FlaskClient.__init__(*args, **kwargs)
|
self._authentication = kwargs.pop("authentication")
|
||||||
self._authentication = authentication
|
super(CustomClient,self).__init__( *args, **kwargs)
|
||||||
|
|
||||||
app.test_client_class = CustomClient
|
app.test_client_class = CustomClient
|
||||||
client = app.test_client(authentication='Basic ....')
|
client = app.test_client(authentication='Basic ....')
|
||||||
|
|
@ -960,7 +980,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.
|
||||||
|
|
@ -1000,6 +1020,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
|
||||||
|
|
@ -1029,8 +1053,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:
|
||||||
|
|
@ -1116,7 +1141,7 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def errorhandler(self, code_or_exception):
|
def errorhandler(self, code_or_exception):
|
||||||
"""A decorator that is used to register a function give a given
|
"""A decorator that is used to register a function given an
|
||||||
error code. Example::
|
error code. Example::
|
||||||
|
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
|
|
@ -1154,7 +1179,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)
|
||||||
|
|
@ -1348,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.
|
||||||
|
|
@ -1437,24 +1463,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, {})
|
||||||
|
|
@ -1556,7 +1571,7 @@ class Flask(_PackageBoundObject):
|
||||||
self.log_exception((exc_type, exc_value, tb))
|
self.log_exception((exc_type, exc_value, tb))
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return InternalServerError()
|
return InternalServerError()
|
||||||
return handler(e)
|
return self.finalize_request(handler(e), from_error_handler=True)
|
||||||
|
|
||||||
def log_exception(self, exc_info):
|
def log_exception(self, exc_info):
|
||||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||||
|
|
@ -1624,9 +1639,30 @@ class Flask(_PackageBoundObject):
|
||||||
rv = self.dispatch_request()
|
rv = self.dispatch_request()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rv = self.handle_user_exception(e)
|
rv = self.handle_user_exception(e)
|
||||||
|
return self.finalize_request(rv)
|
||||||
|
|
||||||
|
def finalize_request(self, rv, from_error_handler=False):
|
||||||
|
"""Given the return value from a view function this finalizes
|
||||||
|
the request by converting it into a response and invoking the
|
||||||
|
postprocessing functions. This is invoked for both normal
|
||||||
|
request dispatching as well as error handlers.
|
||||||
|
|
||||||
|
Because this means that it might be called as a result of a
|
||||||
|
failure a special safe mode is available which can be enabled
|
||||||
|
with the `from_error_handler` flag. If enabled, failures in
|
||||||
|
response processing will be logged and otherwise ignored.
|
||||||
|
|
||||||
|
:internal:
|
||||||
|
"""
|
||||||
response = self.make_response(rv)
|
response = self.make_response(rv)
|
||||||
response = self.process_response(response)
|
try:
|
||||||
request_finished.send(self, response=response)
|
response = self.process_response(response)
|
||||||
|
request_finished.send(self, response=response)
|
||||||
|
except Exception:
|
||||||
|
if not from_error_handler:
|
||||||
|
raise
|
||||||
|
self.logger.exception('Request finalizing failed with an '
|
||||||
|
'error while handling an error')
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def try_trigger_before_first_request_functions(self):
|
def try_trigger_before_first_request_functions(self):
|
||||||
|
|
@ -1973,7 +2009,10 @@ class Flask(_PackageBoundObject):
|
||||||
response = self.full_dispatch_request()
|
response = self.full_dispatch_request()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = e
|
error = e
|
||||||
response = self.make_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):
|
||||||
|
|
|
||||||
37
flask/cli.py
37
flask/cli.py
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
|
@ -86,7 +87,21 @@ def locate_app(app_id):
|
||||||
module = app_id
|
module = app_id
|
||||||
app_obj = None
|
app_obj = None
|
||||||
|
|
||||||
__import__(module)
|
try:
|
||||||
|
__import__(module)
|
||||||
|
except ImportError:
|
||||||
|
# Reraise the ImportError if it occurred within the imported module.
|
||||||
|
# Determine this by checking whether the trace has a depth > 1.
|
||||||
|
if sys.exc_info()[-1].tb_next:
|
||||||
|
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)
|
||||||
|
|
@ -125,9 +140,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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -356,7 +371,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):
|
||||||
|
|
@ -400,6 +417,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)
|
||||||
|
|
@ -423,8 +447,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
|
||||||
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
|
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
|
||||||
|
|
||||||
run_simple(host, port, app, use_reloader=reload,
|
run_simple(host, port, app, use_reloader=reload,
|
||||||
use_debugger=debugger, threaded=with_threads,
|
use_debugger=debugger, threaded=with_threads)
|
||||||
passthrough_errors=True)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command('shell', short_help='Runs a shell in the app context.')
|
@click.command('shell', short_help='Runs a shell in the app context.')
|
||||||
|
|
@ -464,7 +487,7 @@ def shell_command():
|
||||||
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.
|
||||||
|
|
||||||
It loads the application configured (either through the FLASK_APP environment
|
It loads the application configured (through the FLASK_APP environment
|
||||||
variable) and then provides commands either provided by the application or
|
variable) and then provides commands either provided by the application or
|
||||||
Flask itself.
|
Flask itself.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
122
flask/helpers.py
122
flask/helpers.py
|
|
@ -17,6 +17,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
|
||||||
|
|
||||||
|
|
@ -25,8 +26,9 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urlparse import quote as url_quote
|
from urlparse import quote as url_quote
|
||||||
|
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers, Range
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest, NotFound, \
|
||||||
|
RequestedRangeNotSatisfiable
|
||||||
|
|
||||||
# this was moved in 0.7
|
# this was moved in 0.7
|
||||||
try:
|
try:
|
||||||
|
|
@ -39,7 +41,7 @@ from jinja2 import FileSystemLoader
|
||||||
from .signals import message_flashed
|
from .signals import message_flashed
|
||||||
from .globals import session, _request_ctx_stack, _app_ctx_stack, \
|
from .globals import session, _request_ctx_stack, _app_ctx_stack, \
|
||||||
current_app, request
|
current_app, request
|
||||||
from ._compat import string_types, text_type, PY2
|
from ._compat import string_types, text_type
|
||||||
|
|
||||||
|
|
||||||
# sentinel
|
# sentinel
|
||||||
|
|
@ -57,7 +59,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):
|
||||||
|
|
@ -329,6 +331,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:
|
||||||
|
|
@ -437,7 +440,18 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
to ``True`` to directly emit an ``X-Sendfile`` header. This however
|
to ``True`` to directly emit an ``X-Sendfile`` header. This however
|
||||||
requires support of the underlying webserver for ``X-Sendfile``.
|
requires support of the underlying webserver for ``X-Sendfile``.
|
||||||
|
|
||||||
You must explicitly provide the mimetype for the filename or file object.
|
By default it will try to guess the mimetype for you, but you can
|
||||||
|
also explicitly provide one. For extra security you probably want
|
||||||
|
to send certain files as attachment (HTML for instance). The mimetype
|
||||||
|
guessing requires a `filename` or an `attachment_filename` to be
|
||||||
|
provided.
|
||||||
|
|
||||||
|
ETags will also be attached automatically if a `filename` is provided. You
|
||||||
|
can turn this off by setting `add_etags=False`.
|
||||||
|
|
||||||
|
If `conditional=True` and `filename` is provided, this method will try to
|
||||||
|
upgrade the response stream to support range requests. This will allow
|
||||||
|
the request to be answered with partial content response.
|
||||||
|
|
||||||
Please never pass filenames to this function from user sources;
|
Please never pass filenames to this function from user sources;
|
||||||
you should use :func:`send_from_directory` instead.
|
you should use :func:`send_from_directory` instead.
|
||||||
|
|
@ -458,11 +472,20 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
cache_timeout pulls its default from application config, when None.
|
cache_timeout pulls its default from application config, when None.
|
||||||
|
|
||||||
.. versionchanged:: 0.12
|
.. versionchanged:: 0.12
|
||||||
mimetype guessing and etag support removed for file objects.
|
The filename is no longer automatically inferred from file objects. If
|
||||||
If no mimetype or attachment_filename is provided, application/octet-stream
|
you want to use automatic mimetype and etag support, pass a filepath via
|
||||||
will be used.
|
`filename_or_fp` or `attachment_filename`.
|
||||||
|
|
||||||
:param filename_or_fp: the filename of the file to send in `latin-1`.
|
.. versionchanged:: 0.12
|
||||||
|
The `attachment_filename` is preferred over `filename` for MIME-type
|
||||||
|
detection.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.13
|
||||||
|
UTF-8 filenames, as specified in `RFC 2231`_, are supported.
|
||||||
|
|
||||||
|
.. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4
|
||||||
|
|
||||||
|
:param filename_or_fp: the filename of the file to send.
|
||||||
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
|
||||||
|
|
@ -470,8 +493,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
back to the traditional method. Make sure that the
|
back to the traditional method. Make sure that the
|
||||||
file pointer is positioned at the start of data to
|
file pointer is positioned at the start of data to
|
||||||
send before calling :func:`send_file`.
|
send before calling :func:`send_file`.
|
||||||
:param mimetype: the mimetype of the file if provided, otherwise
|
:param mimetype: the mimetype of the file if provided. If a file path is
|
||||||
auto detection happens.
|
given, auto detection happens as fallback, otherwise an
|
||||||
|
error will be raised.
|
||||||
:param as_attachment: set to ``True`` if you want to send this file with
|
:param as_attachment: set to ``True`` if you want to send this file with
|
||||||
a ``Content-Disposition: attachment`` header.
|
a ``Content-Disposition: attachment`` header.
|
||||||
:param attachment_filename: the filename for the attachment if it
|
:param attachment_filename: the filename for the attachment if it
|
||||||
|
|
@ -488,42 +512,62 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
If a file was passed, this overrides its mtime.
|
If a file was passed, this overrides its mtime.
|
||||||
"""
|
"""
|
||||||
mtime = None
|
mtime = None
|
||||||
|
fsize = None
|
||||||
if isinstance(filename_or_fp, string_types):
|
if isinstance(filename_or_fp, string_types):
|
||||||
filename = filename_or_fp
|
filename = filename_or_fp
|
||||||
file = None
|
|
||||||
else:
|
|
||||||
file = filename_or_fp
|
|
||||||
filename = getattr(file, 'name', None)
|
|
||||||
|
|
||||||
if filename is not None:
|
|
||||||
if not os.path.isabs(filename):
|
if not os.path.isabs(filename):
|
||||||
filename = os.path.join(current_app.root_path, filename)
|
filename = os.path.join(current_app.root_path, filename)
|
||||||
if mimetype is None and (filename or attachment_filename):
|
file = None
|
||||||
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
|
if attachment_filename is None:
|
||||||
|
attachment_filename = os.path.basename(filename)
|
||||||
|
else:
|
||||||
|
file = filename_or_fp
|
||||||
|
filename = None
|
||||||
|
|
||||||
if mimetype is None:
|
if mimetype is None:
|
||||||
mimetype = 'application/octet-stream'
|
if attachment_filename is not None:
|
||||||
|
mimetype = mimetypes.guess_type(attachment_filename)[0] \
|
||||||
|
or 'application/octet-stream'
|
||||||
|
|
||||||
|
if mimetype is None:
|
||||||
|
raise ValueError(
|
||||||
|
'Unable to infer MIME-type because no filename is available. '
|
||||||
|
'Please set either `attachment_filename`, pass a filepath to '
|
||||||
|
'`filename_or_fp` or set your own MIME-type via `mimetype`.'
|
||||||
|
)
|
||||||
|
|
||||||
headers = Headers()
|
headers = Headers()
|
||||||
if as_attachment:
|
if as_attachment:
|
||||||
if attachment_filename is None:
|
if attachment_filename is None:
|
||||||
if filename is None:
|
raise TypeError('filename unavailable, required for '
|
||||||
raise TypeError('filename unavailable, required for '
|
'sending as attachment')
|
||||||
'sending as attachment')
|
|
||||||
attachment_filename = os.path.basename(filename)
|
try:
|
||||||
headers.add('Content-Disposition', 'attachment',
|
attachment_filename = attachment_filename.encode('latin-1')
|
||||||
filename=attachment_filename)
|
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:
|
||||||
file.close()
|
file.close()
|
||||||
headers['X-Sendfile'] = filename
|
headers['X-Sendfile'] = filename
|
||||||
headers['Content-Length'] = os.path.getsize(filename)
|
fsize = os.path.getsize(filename)
|
||||||
|
headers['Content-Length'] = fsize
|
||||||
data = None
|
data = None
|
||||||
else:
|
else:
|
||||||
if file is None:
|
if file is None:
|
||||||
file = open(filename, 'rb')
|
file = open(filename, 'rb')
|
||||||
mtime = os.path.getmtime(filename)
|
mtime = os.path.getmtime(filename)
|
||||||
headers['Content-Length'] = os.path.getsize(filename)
|
fsize = os.path.getsize(filename)
|
||||||
|
headers['Content-Length'] = fsize
|
||||||
data = wrap_file(request.environ, file)
|
data = wrap_file(request.environ, file)
|
||||||
|
|
||||||
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
||||||
|
|
@ -541,7 +585,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
rv.cache_control.max_age = cache_timeout
|
rv.cache_control.max_age = cache_timeout
|
||||||
rv.expires = int(time() + cache_timeout)
|
rv.expires = int(time() + cache_timeout)
|
||||||
|
|
||||||
if add_etags and filename is not None and file is None:
|
if add_etags and filename is not None:
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -557,12 +601,22 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
warn('Access %s failed, maybe it does not exist, so ignore etags in '
|
warn('Access %s failed, maybe it does not exist, so ignore etags in '
|
||||||
'headers' % filename, stacklevel=2)
|
'headers' % filename, stacklevel=2)
|
||||||
|
|
||||||
if conditional:
|
if conditional:
|
||||||
|
if callable(getattr(Range, 'to_content_range_header', None)):
|
||||||
|
# Werkzeug supports Range Requests
|
||||||
|
# Remove this test when support for Werkzeug <0.12 is dropped
|
||||||
|
try:
|
||||||
|
rv = rv.make_conditional(request, accept_ranges=True,
|
||||||
|
complete_length=fsize)
|
||||||
|
except RequestedRangeNotSatisfiable:
|
||||||
|
file.close()
|
||||||
|
raise
|
||||||
|
else:
|
||||||
rv = rv.make_conditional(request)
|
rv = rv.make_conditional(request)
|
||||||
# make sure we don't send x-sendfile for servers that
|
# make sure we don't send x-sendfile for servers that
|
||||||
# ignore the 304 status code for x-sendfile.
|
# ignore the 304 status code for x-sendfile.
|
||||||
if rv.status_code == 304:
|
if rv.status_code == 304:
|
||||||
rv.headers.pop('x-sendfile', None)
|
rv.headers.pop('x-sendfile', None)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -236,11 +236,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 +247,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 = (', ', ': ')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,4 +87,8 @@ def create_logger(app):
|
||||||
logger.__class__ = DebugLogger
|
logger.__class__ = DebugLogger
|
||||||
logger.addHandler(debug_handler)
|
logger.addHandler(debug_handler)
|
||||||
logger.addHandler(prod_handler)
|
logger.addHandler(prod_handler)
|
||||||
|
|
||||||
|
# Disable propagation by default
|
||||||
|
logger.propagate = False
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
|
||||||
|
|
@ -84,21 +84,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 +172,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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue