Browse Source

Merge pull request #3 from pallets/master

update
pull/2105/head
Marcelo Ferreira 8 years ago committed by GitHub
parent
commit
e1639f3e0b
  1. 1
      .gitattributes
  2. 2
      .github/ISSUE_TEMPLATE.rst
  3. 6
      .gitignore
  4. 9
      .travis.yml
  5. 1
      AUTHORS
  6. 23
      CHANGES
  7. 46
      CONTRIBUTING.rst
  8. 6
      Makefile
  9. 2
      README
  10. 22
      docs/advanced_foreword.rst
  11. 8
      docs/api.rst
  12. 5
      docs/appcontext.rst
  13. 4
      docs/becomingbig.rst
  14. 21
      docs/blueprints.rst
  15. 16
      docs/cli.rst
  16. 78
      docs/conf.py
  17. 5
      docs/config.rst
  18. 2
      docs/deploying/index.rst
  19. 8
      docs/deploying/mod_wsgi.rst
  20. 2
      docs/deploying/uwsgi.rst
  21. 4
      docs/deploying/wsgi-standalone.rst
  22. 13
      docs/errorhandling.rst
  23. 7
      docs/extensions.rst
  24. 49
      docs/installation.rst
  25. 2
      docs/patterns/celery.rst
  26. 45
      docs/patterns/distribute.rst
  27. 38
      docs/patterns/fabric.rst
  28. 10
      docs/patterns/fileuploads.rst
  29. 6
      docs/patterns/flashing.rst
  30. 1
      docs/patterns/index.rst
  31. 15
      docs/patterns/lazyloading.rst
  32. 2
      docs/patterns/sqlalchemy.rst
  33. 21
      docs/patterns/sqlite3.rst
  34. 17
      docs/patterns/subclassing.rst
  35. 27
      docs/patterns/viewdecorators.rst
  36. 6
      docs/patterns/wtforms.rst
  37. 39
      docs/python3.rst
  38. 131
      docs/quickstart.rst
  39. 14
      docs/testing.rst
  40. 2
      docs/tutorial/dbinit.rst
  41. 2
      docs/tutorial/folders.rst
  42. 2
      docs/tutorial/packaging.rst
  43. 62
      docs/upgrading.rst
  44. 2
      examples/flaskr/flaskr/__init__.py
  45. 4
      examples/flaskr/flaskr/templates/show_entries.html
  46. 2
      examples/flaskr/setup.cfg
  47. 2
      examples/minitwit/minitwit/__init__.py
  48. 2
      flask/__init__.py
  49. 78
      flask/app.py
  50. 47
      flask/cli.py
  51. 11
      flask/config.py
  52. 2
      flask/exthook.py
  53. 146
      flask/helpers.py
  54. 5
      flask/json.py
  55. 4
      flask/logging.py
  56. 2
      flask/sessions.py
  57. 2
      flask/signals.py
  58. 14
      flask/testing.py
  59. 2
      flask/views.py
  60. 3
      scripts/flask-07-upgrade.py
  61. 5
      scripts/make-release.py
  62. 7
      setup.cfg
  63. 2
      setup.py
  64. 1
      test-requirements.txt
  65. 11
      tests/conftest.py
  66. 57
      tests/test_basic.py
  67. 79
      tests/test_cli.py
  68. 20
      tests/test_deprecations.py
  69. 12
      tests/test_ext.py
  70. 300
      tests/test_helpers.py
  71. 35
      tests/test_testing.py
  72. 15
      tox.ini

1
.gitattributes vendored

@ -0,0 +1 @@
CHANGES merge=union

2
.github/ISSUE_TEMPLATE.rst

@ -0,0 +1,2 @@
The issue tracker is a tool to address bugs.
Please use the #pocoo IRC channel on freenode or Stack Overflow for questions.

6
.gitignore vendored

@ -11,3 +11,9 @@ _mailinglist
.tox .tox
.cache/ .cache/
.idea/ .idea/
# Coverage reports
htmlcov
.coverage
.coverage.*
*,cover

9
.travis.yml

@ -11,18 +11,27 @@ python:
env: env:
- REQUIREMENTS=lowest - REQUIREMENTS=lowest
- REQUIREMENTS=lowest-simplejson
- REQUIREMENTS=release - REQUIREMENTS=release
- REQUIREMENTS=release-simplejson
- REQUIREMENTS=devel - REQUIREMENTS=devel
- REQUIREMENTS=devel-simplejson
matrix: matrix:
exclude: exclude:
# Python 3 support currently does not work with lowest requirements # Python 3 support currently does not work with lowest requirements
- python: "3.3" - python: "3.3"
env: REQUIREMENTS=lowest env: REQUIREMENTS=lowest
- python: "3.3"
env: REQUIREMENTS=lowest-simplejson
- python: "3.4" - python: "3.4"
env: REQUIREMENTS=lowest env: REQUIREMENTS=lowest
- python: "3.4"
env: REQUIREMENTS=lowest-simplejson
- python: "3.5" - python: "3.5"
env: REQUIREMENTS=lowest env: REQUIREMENTS=lowest
- python: "3.5"
env: REQUIREMENTS=lowest-simplejson
install: install:

1
AUTHORS

@ -15,6 +15,7 @@ Patches and Suggestions
- Chris Grindstaff - Chris Grindstaff
- Christopher Grebs - Christopher Grebs
- Daniel Neuhäuser - Daniel Neuhäuser
- Dan Sully
- David Lord @davidism - David Lord @davidism
- Edmond Burnett - Edmond Burnett
- Florent Xicluna - Florent Xicluna

23
CHANGES

@ -3,6 +3,25 @@ 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.12
------------
- the cli command now responds to `--version`.
- Mimetype guessing and ETag generation for file-like objects in ``send_file``
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``
(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
-------------- --------------
@ -41,7 +60,7 @@ Released on May 29th 2016, codename Absinthe.
from a view function. from a view function.
- Added :meth:`flask.Config.from_json`. - Added :meth:`flask.Config.from_json`.
- Added :attr:`flask.Flask.config_class`. - Added :attr:`flask.Flask.config_class`.
- Added :meth:`flask.config.Config.get_namespace`. - Added :meth:`flask.Config.get_namespace`.
- Templates are no longer automatically reloaded outside of debug mode. This - Templates are no longer automatically reloaded outside of debug mode. This
can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config key. can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config key.
- Added a workaround for a limitation in Python 3.3's namespace loader. - Added a workaround for a limitation in Python 3.3's namespace loader.
@ -316,7 +335,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.

46
CONTRIBUTING.rst

@ -36,7 +36,7 @@ Running the testsuite
--------------------- ---------------------
You probably want to set up a `virtualenv You probably want to set up a `virtualenv
<http://virtualenv.readthedocs.org/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 ``py.test``. You can
install it with:: install it with::
@ -68,3 +68,47 @@ of ``pytest``. You can install it with::
The ``tox`` command will then run all tests against multiple combinations The ``tox`` command will then run all tests against multiple combinations
Python versions and dependency versions. Python versions and dependency versions.
Running test coverage
---------------------
Generating a report of lines that do not have unit test coverage can indicate where
to start contributing. ``pytest`` integrates with ``coverage.py``, using the ``pytest-cov``
plugin. This assumes you have already run the testsuite (see previous section)::
pip install pytest-cov
After this has been installed, you can output a report to the command line using this command::
py.test --cov=flask tests/
Generate a HTML report can be done using this command::
py.test --cov-report html --cov=flask tests/
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.

6
Makefile

@ -3,10 +3,8 @@
all: clean-pyc test all: clean-pyc test
test: test:
FLASK_DEBUG= py.test tests examples pip install -r test-requirements.txt
tox -e py-release
tox-test:
tox
audit: audit:
python setup.py audit python setup.py audit

2
README

@ -37,6 +37,8 @@
$ py.test $ py.test
Details on contributing can be found in CONTRIBUTING.rst
~ Where can I get help? ~ Where can I get help?
Either use the #pocoo IRC channel on irc.freenode.net or Either use the #pocoo IRC channel on irc.freenode.net or

22
docs/advanced_foreword.rst

@ -46,24 +46,10 @@ spam, links to malicious software, and the like.
Flask is no different from any other framework in that you the developer must Flask is no different from any other framework in that you the developer must
build with caution, watching for exploits when building to your requirements. build with caution, watching for exploits when building to your requirements.
The Status of Python 3 Python 3 Support in Flask
---------------------- -------------------------
Currently the Python community is in the process of improving libraries to
support the new iteration of the Python programming language. While the
situation is greatly improving there are still some issues that make it
hard for users to switch over to Python 3 just now. These problems are
partially caused by changes in the language that went unreviewed for too
long, partially also because we have not quite worked out how the lower-
level API should change to account for the Unicode differences in Python 3.
We strongly recommend using Python 2.7 with activated Python 3
warnings during development. If you plan on upgrading to Python 3 in the
near future we strongly recommend that you read `How to write forwards
compatible Python code
<http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/>`_.
If you do want to dive into Python 3 already have a look at the Flask, its dependencies, and most Flask extensions all support Python 3.
:ref:`python3-support` page. If you want to use Flask with Python 3 have a look at the :ref:`python3-support` page.
Continue to :ref:`installation` or the :ref:`quickstart`. Continue to :ref:`installation` or the :ref:`quickstart`.

8
docs/api.rst

@ -316,13 +316,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

5
docs/appcontext.rst

@ -74,6 +74,11 @@ The application context is also used by the :func:`~flask.url_for`
function in case a ``SERVER_NAME`` was configured. This allows you to function in case a ``SERVER_NAME`` was configured. This allows you to
generate URLs even in the absence of a request. generate URLs even in the absence of a request.
If no request context has been pushed and an application context has
not been explicitly set, a ``RuntimeError`` will be raised. ::
RuntimeError: Working outside of application context.
Locality of the Context Locality of the Context
----------------------- -----------------------

4
docs/becomingbig.rst

@ -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.
@ -35,7 +35,7 @@ Subclass.
The :class:`~flask.Flask` class has many methods designed for subclassing. You The :class:`~flask.Flask` class has many methods designed for subclassing. You
can quickly add or customize behavior by subclassing :class:`~flask.Flask` (see can quickly add or customize behavior by subclassing :class:`~flask.Flask` (see
the linked method docs) and using that subclass wherever you instantiate an the linked method docs) and using that subclass wherever you instantiate an
application class. This works well with :ref:`app-factories`. application class. This works well with :ref:`app-factories`. See :doc:`/patterns/subclassing` for an example.
Wrap with middleware. Wrap with middleware.
--------------------- ---------------------

21
docs/blueprints.rst

@ -176,16 +176,25 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
admin = Blueprint('admin', __name__, template_folder='templates') admin = Blueprint('admin', __name__, template_folder='templates')
As for static files, the path can be absolute or relative to the blueprint For static files, the path can be absolute or relative to the blueprint
resource folder. The template folder is added to the searchpath of resource folder.
templates but with a lower priority than the actual application's template
folder. That way you can easily override templates that a blueprint The template folder is added to the search path of templates but with a lower
provides in the actual application. priority than the actual application's template folder. That way you can
easily override templates that a blueprint provides in the actual application.
This also means that if you don't want a blueprint template to be accidentally
overridden, make sure that no other blueprint or actual application template
has the same relative path. When multiple blueprints provide the same relative
template path the first blueprint registered takes precedence over the others.
So if you have a blueprint in the folder ``yourapplication/admin`` and you So if you have a blueprint in the folder ``yourapplication/admin`` and you
want to render the template ``'admin/index.html'`` and you have provided want to render the template ``'admin/index.html'`` and you have provided
``templates`` as a `template_folder` you will have to create a file like ``templates`` as a `template_folder` you will have to create a file like
this: :file:`yourapplication/admin/templates/admin/index.html`. this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
for the extra ``admin`` folder is to avoid getting our template overridden
by a template named ``index.html`` in the actual application template
folder.
To further reiterate this: if you have a blueprint named ``admin`` and you To further reiterate this: if you have a blueprint named ``admin`` and you
want to render a template called :file:`index.html` which is specific to this want to render a template called :file:`index.html` which is specific to this

16
docs/cli.rst

@ -32,7 +32,7 @@ Python module that contains a Flask application.
In that imported file the name of the app needs to be called ``app`` or In that imported file the name of the app needs to be called ``app`` or
optionally be specified after a colon. For instance optionally be specified after a colon. For instance
`mymodule:application` would tell it to use the `application` object in ``mymodule:application`` would tell it to use the `application` object in
the :file:`mymodule.py` file. the :file:`mymodule.py` file.
Given a :file:`hello.py` file with the application in it named ``app`` Given a :file:`hello.py` file with the application in it named ``app``
@ -61,9 +61,7 @@ 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
@ -218,13 +216,13 @@ step.
CLI Plugins CLI Plugins
----------- -----------
Flask extensions can always patch the `Flask.cli` instance with more Flask extensions can always patch the :attr:`Flask.cli` instance with more
commands if they want. However there is a second way to add CLI plugins commands if they want. However there is a second way to add CLI plugins
to Flask which is through `setuptools`. If you make a Python package that to Flask which is through ``setuptools``. If you make a Python package that
should export a Flask command line plugin you can ship a `setup.py` file should export a Flask command line plugin you can ship a :file:`setup.py` file
that declares an entrypoint that points to a click command: that declares an entrypoint that points to a click command:
Example `setup.py`:: Example :file:`setup.py`::
from setuptools import setup from setuptools import setup
@ -237,7 +235,7 @@ Example `setup.py`::
''', ''',
) )
Inside `mypackage/comamnds.py` you can then export a Click object:: Inside :file:`mypackage/commands.py` you can then export a Click object::
import click import click

78
docs/conf.py

@ -11,13 +11,16 @@
# 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
import sys, os from datetime import datetime
import os
import sys
import pkg_resources
# 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
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('_themes')) sys.path.append(os.path.join(os.path.dirname(__file__), '_themes'))
sys.path.append(os.path.abspath('.')) sys.path.append(os.path.dirname(__file__))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
@ -46,22 +49,21 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Flask' project = u'Flask'
copyright = u'2015, Armin Ronacher' copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().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
# built documents. # built documents.
import pkg_resources
try: try:
release = pkg_resources.get_distribution('Flask').version release = pkg_resources.get_distribution('Flask').version
except pkg_resources.DistributionNotFound: except pkg_resources.DistributionNotFound:
print('Flask must be installed to build the documentation.') print('Flask must be installed to build the documentation.')
print('Install from source using `pip install -e .` in a virtualenv.') print('Install from source using `pip install -e .` in a virtualenv.')
sys.exit(1) sys.exit(1)
del pkg_resources
if 'dev' in release: if 'dev' in release:
release = release.split('dev')[0] + 'dev' release = ''.join(release.partition('dev')[:2])
version = '.'.join(release.split('.')[:2]) version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
@ -100,14 +102,12 @@ exclude_patterns = ['_build']
# The theme to use for HTML and HTML Help pages. Major themes that come with # The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'. # Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'flask' # html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
html_theme_options = { # html_theme_options = {}
'touch_icon': 'touch-icon.png'
}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes'] html_theme_path = ['_themes']
@ -126,7 +126,7 @@ html_theme_path = ['_themes']
# The name of an image file (within the static path) to use as favicon of the # The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
html_favicon = "flask-favicon.ico" html_favicon = '_static/flask-favicon.ico'
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
@ -143,9 +143,18 @@ html_static_path = ['_static']
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
html_sidebars = { html_sidebars = {
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], 'index': [
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 'sidebarintro.html',
'sourcelink.html', 'searchbox.html'] 'sourcelink.html',
'searchbox.html'
],
'**': [
'sidebarlogo.html',
'localtoc.html',
'relations.html',
'sourcelink.html',
'searchbox.html'
]
} }
# Additional templates that should be rendered to pages, maps page names to # Additional templates that should be rendered to pages, maps page names to
@ -187,8 +196,7 @@ htmlhelp_basename = 'Flaskdoc'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('latexindex', 'Flask.tex', u'Flask Documentation', ('latexindex', 'Flask.tex', u'Flask Documentation', u'Armin Ronacher', 'manual'),
u'Armin Ronacher', 'manual'),
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
@ -198,10 +206,10 @@ latex_documents = [
latex_use_modindex = False latex_use_modindex = False
latex_elements = { latex_elements = {
'fontpkg': r'\usepackage{mathpazo}', 'fontpkg': r'\usepackage{mathpazo}',
'papersize': 'a4paper', 'papersize': 'a4paper',
'pointsize': '12pt', 'pointsize': '12pt',
'preamble': r'\usepackage{flaskstyle}' 'preamble': r'\usepackage{flaskstyle}'
} }
latex_use_parts = True latex_use_parts = True
@ -245,21 +253,23 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
#epub_tocdepth = 3 #epub_tocdepth = 3
intersphinx_mapping = { intersphinx_mapping = {
'https://docs.python.org/dev': None, 'python': ('https://docs.python.org/3/', None),
'http://werkzeug.pocoo.org/docs/': None, 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
'http://click.pocoo.org/': None, 'click': ('http://click.pocoo.org/', None),
'http://jinja.pocoo.org/docs/': None, 'jinja': ('http://jinja.pocoo.org/docs/', None),
'http://www.sqlalchemy.org/docs/': None, 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
'https://wtforms.readthedocs.org/en/latest/': None, 'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
'https://pythonhosted.org/blinker/': None 'blinker': ('https://pythonhosted.org/blinker/', None)
} }
pygments_style = 'flask_theme_support.FlaskyStyle'
# fall back if theme is not there
try: try:
__import__('flask_theme_support') __import__('flask_theme_support')
except ImportError as e: pygments_style = 'flask_theme_support.FlaskyStyle'
html_theme = 'flask'
html_theme_options = {
'touch_icon': 'touch-icon.png'
}
except ImportError:
print('-' * 74) print('-' * 74)
print('Warning: Flask themes unavailable. Building with default theme') print('Warning: Flask themes unavailable. Building with default theme')
print('If you want the Flask themes, run this command and build again:') print('If you want the Flask themes, run this command and build again:')
@ -267,10 +277,6 @@ except ImportError as e:
print(' git submodule update --init') print(' git submodule update --init')
print('-' * 74) print('-' * 74)
pygments_style = 'tango'
html_theme = 'default'
html_theme_options = {}
# unwrap decorators # unwrap decorators
def unwrap_decorators(): def unwrap_decorators():

5
docs/config.rst

@ -177,7 +177,7 @@ 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`` (the default)
jsonify responses will be pretty printed jsonify responses will be pretty printed
if they are not requested by an if they are not requested by an
@ -262,7 +262,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::
@ -310,6 +310,7 @@ that experience:
limit yourself to request-only accesses to the configuration you can limit yourself to request-only accesses to the configuration you can
reconfigure the object later on as needed. reconfigure the object later on as needed.
.. _config-dev-prod:
Development / Production Development / Production
------------------------ ------------------------

2
docs/deploying/index.rst

@ -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 <http://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
------------------- -------------------

8
docs/deploying/mod_wsgi.rst

@ -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: http://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: http://modwsgi.readthedocs.io/en/develop/index.html
Troubleshooting Troubleshooting
--------------- ---------------

2
docs/deploying/uwsgi.rst

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

4
docs/deploying/wsgi-standalone.rst

@ -25,7 +25,7 @@ For example, to run a Flask application with 4 worker processes (``-w
.. _Gunicorn: http://gunicorn.org/ .. _Gunicorn: http://gunicorn.org/
.. _eventlet: http://eventlet.net/ .. _eventlet: http://eventlet.net/
.. _greenlet: http://greenlet.readthedocs.org/en/latest/ .. _greenlet: https://greenlet.readthedocs.io/en/latest/
Gevent Gevent
------- -------
@ -41,7 +41,7 @@ event loop::
http_server.serve_forever() http_server.serve_forever()
.. _Gevent: http://www.gevent.org/ .. _Gevent: http://www.gevent.org/
.. _greenlet: http://greenlet.readthedocs.org/en/latest/ .. _greenlet: https://greenlet.readthedocs.io/en/latest/
.. _libev: http://software.schmorp.de/pkg/libev.html .. _libev: http://software.schmorp.de/pkg/libev.html
Twisted Web Twisted Web

13
docs/errorhandling.rst

@ -36,7 +36,7 @@ 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 <http://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
<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
aggregates duplicate errors, captures the full stack trace and local aggregates duplicate errors, captures the full stack trace and local
variables for debugging, and sends you mails based on new errors or variables for debugging, and sends you mails based on new errors or
@ -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 |

7
docs/extensions.rst

@ -25,6 +25,13 @@ importable from ``flask_foo``::
import flask_foo import flask_foo
Building Extensions
-------------------
While `Flask Extension Registry`_ contains many Flask extensions, you may not find
an extension that fits your need. If this is the case, you can always create your own.
Consider reading :ref:`extension-dev` to develop your own Flask extension.
Flask Before 0.8 Flask Before 0.8
---------------- ----------------

49
docs/installation.rst

@ -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,19 +95,19 @@ 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::
@ -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 `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 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

2
docs/patterns/celery.rst

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

45
docs/patterns/distribute.rst

@ -39,10 +39,8 @@ the process, also read the :ref:`fabric-deployment` chapter.
Basic Setup Script Basic Setup Script
------------------ ------------------
Because you have Flask running, you have setuptools available on your system anyways. Because you have Flask installed, you have setuptools available on your system.
Flask already depends upon setuptools. If you do not, fear not, there is a Flask already depends upon setuptools.
script to install it for you: `ez_setup.py`_. Just download and
run with your Python interpreter.
Standard disclaimer applies: :ref:`you better use a virtualenv Standard disclaimer applies: :ref:`you better use a virtualenv
<virtualenv>`. <virtualenv>`.
@ -67,7 +65,7 @@ A basic :file:`setup.py` file for a Flask application looks like this::
Please keep in mind that you have to list subpackages explicitly. If you Please keep in mind that you have to list subpackages explicitly. If you
want setuptools to lookup the packages for you automatically, you can use want setuptools to lookup the packages for you automatically, you can use
the `find_packages` function:: the ``find_packages`` function::
from setuptools import setup, find_packages from setuptools import setup, find_packages
@ -76,17 +74,36 @@ the `find_packages` function::
packages=find_packages() packages=find_packages()
) )
Most parameters to the `setup` function should be self explanatory, Most parameters to the ``setup`` function should be self explanatory,
`include_package_data` and `zip_safe` might not be. ``include_package_data`` and ``zip_safe`` might not be.
`include_package_data` tells setuptools to look for a :file:`MANIFEST.in` file ``include_package_data`` tells setuptools to look for a :file:`MANIFEST.in` file
and install all the entries that match as package data. We will use this and install all the entries that match as package data. We will use this
to distribute the static files and templates along with the Python module to distribute the static files and templates along with the Python module
(see :ref:`distributing-resources`). The `zip_safe` flag can be used to (see :ref:`distributing-resources`). The ``zip_safe`` flag can be used to
force or prevent zip Archive creation. In general you probably don't want force or prevent zip Archive creation. In general you probably don't want
your packages to be installed as zip files because some tools do not your packages to be installed as zip files because some tools do not
support them and they make debugging a lot harder. support them and they make debugging a lot harder.
Tagging Builds
--------------
It is useful to distinguish between release and development builds. Add a
:file:`setup.cfg` file to configure these options.
[egg_info]
tag_build = .dev
tag_date = 1
[aliases]
release = egg_info -RDb ''
Running ``python setup.py sdist`` will create a development package
with ".dev" and the current date appended: ``flaskr-1.0.dev20160314.tar.gz``.
Running ``python setup.py release sdist`` will create a release package
with only the version: ``flaskr-1.0.tar.gz``.
.. _distributing-resources: .. _distributing-resources:
Distributing Resources Distributing Resources
@ -104,13 +121,13 @@ your tarball::
Don't forget that even if you enlist them in your :file:`MANIFEST.in` file, they Don't forget that even if you enlist them in your :file:`MANIFEST.in` file, they
won't be installed for you unless you set the `include_package_data` won't be installed for you unless you set the `include_package_data`
parameter of the `setup` function to ``True``! parameter of the ``setup`` function to ``True``!
Declaring Dependencies Declaring Dependencies
---------------------- ----------------------
Dependencies are declared in the `install_requires` parameter as a list. Dependencies are declared in the ``install_requires`` parameter as a list.
Each item in that list is the name of a package that should be pulled from Each item in that list is the name of a package that should be pulled from
PyPI on installation. By default it will always use the most recent PyPI on installation. By default it will always use the most recent
version, but you can also provide minimum and maximum version version, but you can also provide minimum and maximum version
@ -140,20 +157,20 @@ Installing / Developing
----------------------- -----------------------
To install your application (ideally into a virtualenv) just run the To install your application (ideally into a virtualenv) just run the
:file:`setup.py` script with the `install` parameter. It will install your :file:`setup.py` script with the ``install`` parameter. It will install your
application into the virtualenv's site-packages folder and also download application into the virtualenv's site-packages folder and also download
and install all dependencies:: and install all dependencies::
$ python setup.py install $ python setup.py install
If you are developing on the package and also want the requirements to be If you are developing on the package and also want the requirements to be
installed, you can use the `develop` command instead:: installed, you can use the ``develop`` command instead::
$ python setup.py develop $ python setup.py develop
This has the advantage of just installing a link to the site-packages This has the advantage of just installing a link to the site-packages
folder instead of copying the data over. You can then continue to work on folder instead of copying the data over. You can then continue to work on
the code without having to run `install` again after each change. 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

38
docs/patterns/fabric.rst

@ -43,36 +43,25 @@ virtual environment::
env.hosts = ['server1.example.com', 'server2.example.com'] env.hosts = ['server1.example.com', 'server2.example.com']
def pack(): def pack():
# create a new source distribution as tarball # build the package
local('python setup.py sdist --formats=gztar', capture=False) local('python setup.py sdist --formats=gztar', capture=False)
def deploy(): def deploy():
# figure out the release name and version # figure out the package name and version
dist = local('python setup.py --fullname', capture=True).strip() dist = local('python setup.py --fullname', capture=True).strip()
# upload the source tarball to the temporary folder on the server filename = '%s.tar.gz' % dist
put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
# create a place where we can unzip the tarball, then enter # upload the package to the temporary folder on the server
# that directory and unzip it put('dist/%s' % filename, '/tmp/%s' % filename)
run('mkdir /tmp/yourapplication')
with cd('/tmp/yourapplication'):
run('tar xzf /tmp/yourapplication.tar.gz')
# now setup the package with our virtual environment's
# python interpreter
run('/var/www/yourapplication/env/bin/python setup.py install')
# now that all is set up, delete the folder again
run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
# and finally touch the .wsgi file so that mod_wsgi triggers
# a reload of the application
run('touch /var/www/yourapplication.wsgi')
The example above is well documented and should be straightforward. Here # install the package in the application's virtualenv with pip
a recap of the most common commands fabric provides: run('/var/www/yourapplication/env/bin/pip install /tmp/%s' % filename)
- `run` - executes a command on a remote server # remove the uploaded package
- `local` - executes a command on the local machine run('rm -r /tmp/%s' % filename)
- `put` - uploads a file to the remote server
- `cd` - changes the directory on the serverside. This has to be used # touch the .wsgi file to trigger a reload in mod_wsgi
in combination with the ``with`` statement. run('touch /var/www/yourapplication.wsgi')
Running Fabfiles Running Fabfiles
---------------- ----------------
@ -156,6 +145,7 @@ location where it's expected (eg: :file:`/var/www/yourapplication`).
Either way, in our case here we only expect one or two servers and we can Either way, in our case here we only expect one or two servers and we can
upload them ahead of time by hand. upload them ahead of time by hand.
First Deployment First Deployment
---------------- ----------------

10
docs/patterns/fileuploads.rst

@ -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():
@ -71,7 +71,7 @@ the file and redirects the user to the URL for the uploaded file::
<!doctype html> <!doctype html>
<title>Upload new File</title> <title>Upload new File</title>
<h1>Upload new File</h1> <h1>Upload new File</h1>
<form action="" method=post enctype=multipart/form-data> <form method=post enctype=multipart/form-data>
<p><input type=file name=file> <p><input type=file name=file>
<input type=submit value=Upload> <input type=submit value=Upload>
</form> </form>
@ -104,9 +104,9 @@ before storing it directly on the filesystem.
>>> secure_filename('../../../../home/username/.bashrc') >>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc' 'home_username_.bashrc'
Now one last thing is missing: the serving of the uploaded files. In the Now one last thing is missing: the serving of the uploaded files. In the
:func:`upload_file()` we redirect the user to :func:`upload_file()` we redirect the user to
``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``. ``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``.
So we write the :func:`uploaded_file` function to return the file of that name. As So we write the :func:`uploaded_file` function to return the file of that name. As
of Flask 0.5 we can use a function that does that for us:: of Flask 0.5 we can use a function that does that for us::

6
docs/patterns/flashing.rst

@ -9,7 +9,9 @@ application. Flask provides a really simple way to give feedback to a
user with the flashing system. The flashing system basically makes it user with the flashing system. The flashing system basically makes it
possible to record a message at the end of a request and access it next possible to record a message at the end of a request and access it next
request and only next request. This is usually combined with a layout request and only next request. This is usually combined with a layout
template that does this. template that does this. Note that browsers and sometimes web servers enforce
a limit on cookie sizes. This means that flashing messages that are too
large for session cookies causes message flashing to fail silently.
Simple Flashing Simple Flashing
--------------- ---------------
@ -76,7 +78,7 @@ And here is the :file:`login.html` template which also inherits from
{% if error %} {% if error %}
<p class=error><strong>Error:</strong> {{ error }} <p class=error><strong>Error:</strong> {{ error }}
{% endif %} {% endif %}
<form action="" method=post> <form method=post>
<dl> <dl>
<dt>Username: <dt>Username:
<dd><input type=text name=username value="{{ <dd><input type=text name=username value="{{

1
docs/patterns/index.rst

@ -41,3 +41,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
methodoverrides methodoverrides
requestchecksum requestchecksum
celery celery
subclassing

15
docs/patterns/lazyloading.rst

@ -90,14 +90,19 @@ Then you can define your central place to combine the views like this::
You can further optimize this in terms of amount of keystrokes needed to You can further optimize this in terms of amount of keystrokes needed to
write this by having a function that calls into write this by having a function that calls into
:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project :meth:`~flask.Flask.add_url_rule` by prefixing a string with the project
name and a dot, and by wrapping `view_func` in a `LazyView` as needed:: name and a dot, and by wrapping `view_func` in a `LazyView` as needed. ::
def url(url_rule, import_name, **options): def url(import_name, url_rules=[], **options):
view = LazyView('yourapplication.' + import_name) view = LazyView('yourapplication.' + import_name)
app.add_url_rule(url_rule, view_func=view, **options) for url_rule in url_rules:
app.add_url_rule(url_rule, view_func=view, **options)
url('/', 'views.index') # add a single route to the index view
url('/user/<username>', 'views.user') url('views.index', ['/'])
# add two routes to a single function endpoint
url_rules = ['/user/','/user/<username>']
url('views.user', url_rules)
One thing to keep in mind is that before and after request handlers have One thing to keep in mind is that before and after request handlers have
to be in a file that is imported upfront to work properly on the first to be in a file that is imported upfront to work properly on the first

2
docs/patterns/sqlalchemy.rst

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

21
docs/patterns/sqlite3.rst

@ -71,7 +71,8 @@ Now in each request handling function you can access `g.db` to get the
current open database connection. To simplify working with SQLite, a current open database connection. To simplify working with SQLite, a
row factory function is useful. It is executed for every result returned row factory function is useful. It is executed for every result returned
from the database to convert the result. For instance, in order to get from the database to convert the result. For instance, in order to get
dictionaries instead of tuples, this could be inserted into ``get_db``:: dictionaries instead of tuples, this could be inserted into the ``get_db``
function we created above::
def make_dicts(cursor, row): def make_dicts(cursor, row):
return dict((cursor.description[idx][0], value) return dict((cursor.description[idx][0], value)
@ -79,10 +80,26 @@ dictionaries instead of tuples, this could be inserted into ``get_db``::
db.row_factory = make_dicts db.row_factory = make_dicts
Or even simpler:: This will make the sqlite3 module return dicts for this database connection, which are much nicer to deal with. Even more simply, we could place this in ``get_db`` instead::
db.row_factory = sqlite3.Row db.row_factory = sqlite3.Row
This would use Row objects rather than dicts to return the results of queries. These are ``namedtuple`` s, so we can access them either by index or by key. For example, assuming we have a ``sqlite3.Row`` called ``r`` for the rows ``id``, ``FirstName``, ``LastName``, and ``MiddleInitial``::
>>> # You can get values based on the row's name
>>> r['FirstName']
John
>>> # Or, you can get them based on index
>>> r[1]
John
# Row objects are also iterable:
>>> for value in r:
... print(value)
1
John
Doe
M
Additionally, it is a good idea to provide a query function that combines Additionally, it is a good idea to provide a query function that combines
getting the cursor, executing and fetching the results:: getting the cursor, executing and fetching the results::

17
docs/patterns/subclassing.rst

@ -0,0 +1,17 @@
Subclassing Flask
=================
The :class:`~flask.Flask` class is designed for subclassing.
For example, you may want to override how request parameters are handled to preserve their order::
from flask import Flask, Request
from werkzeug.datastructures import ImmutableOrderedMultiDict
class MyRequest(Request):
"""Request subclass to override request parameter storage"""
parameter_storage_class = ImmutableOrderedMultiDict
class MyFlask(Flask):
"""Flask subclass using the custom request class"""
request_class = MyRequest
This is the recommended approach for overriding or augmenting Flask's internal functionality.

27
docs/patterns/viewdecorators.rst

@ -16,15 +16,13 @@ Login Required Decorator
------------------------ ------------------------
So let's implement such a decorator. A decorator is a function that So let's implement such a decorator. A decorator is a function that
returns a function. Pretty simple actually. The only thing you have to wraps and replaces another function. Since the original function is
keep in mind when implementing something like this is to update the replaced, you need to remember to copy the original function's information
`__name__`, `__module__` and some other attributes of a function. This is to the new function. Use :func:`functools.wraps` to handle this for you.
often forgotten, but you don't have to do that by hand, there is a
function for that that is used like a decorator (:func:`functools.wraps`).
This example assumes that the login page is called ``'login'`` and that This example assumes that the login page is called ``'login'`` and that
the current user is stored as `g.user` and ``None`` if there is no-one the current user is stored in ``g.user`` and is ``None`` if there is no-one
logged in:: logged in. ::
from functools import wraps from functools import wraps
from flask import g, request, redirect, url_for from flask import g, request, redirect, url_for
@ -37,15 +35,24 @@ logged in::
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function return decorated_function
So how would you use that decorator now? Apply it as innermost decorator To use the decorator, apply it as innermost decorator to a view function.
to a view function. When applying further decorators, always remember When applying further decorators, always remember
that the :meth:`~flask.Flask.route` decorator is the outermost:: that the :meth:`~flask.Flask.route` decorator is the outermost. ::
@app.route('/secret_page') @app.route('/secret_page')
@login_required @login_required
def secret_page(): def secret_page():
pass pass
.. note::
The ``next`` value will exist in ``request.args`` after a ``GET`` request for
the login page. You'll have to pass it along when sending the ``POST`` request
from the login form. You can do this with a hidden input tag, then retrieve it
from ``request.form`` when logging the user in. ::
<input type="hidden" value="{{ request.args.get('next', '') }}"/>
Caching Decorator Caching Decorator
----------------- -----------------

6
docs/patterns/wtforms.rst

@ -108,7 +108,7 @@ takes advantage of the :file:`_formhelpers.html` template:
.. sourcecode:: html+jinja .. sourcecode:: html+jinja
{% from "_formhelpers.html" import render_field %} {% from "_formhelpers.html" import render_field %}
<form method=post action="/register"> <form method=post>
<dl> <dl>
{{ render_field(form.username) }} {{ render_field(form.username) }}
{{ render_field(form.email) }} {{ render_field(form.email) }}
@ -122,5 +122,5 @@ takes advantage of the :file:`_formhelpers.html` template:
For more information about WTForms, head over to the `WTForms For more information about WTForms, head over to the `WTForms
website`_. website`_.
.. _WTForms: http://wtforms.readthedocs.org/ .. _WTForms: https://wtforms.readthedocs.io/
.. _WTForms website: http://wtforms.readthedocs.org/ .. _WTForms website: https://wtforms.readthedocs.io/

39
docs/python3.rst

@ -3,32 +3,21 @@
Python 3 Support Python 3 Support
================ ================
Flask and all of its dependencies support Python 3 so you can in theory Flask, its dependencies, and most Flask extensions support Python 3.
start working on it already. There are however a few things you should be You should start using Python 3 for your next project,
aware of before you start using Python 3 for your next project. but there are a few things to be aware of.
If you want to use Flask with Python 3 you will need to use Python 3.3 or You need to use Python 3.3 or higher. 3.2 and older are *not* supported.
higher. 3.2 and older are *not* supported.
In addition to that you need to use the latest and greatest versions of You should use the latest versions of all Flask-related packages.
`itsdangerous`, `Jinja2` and `Werkzeug`. Flask 0.10 and Werkzeug 0.9 were Flask 0.10 and Werkzeug 0.9 were the first versions to introduce Python 3 support.
the first versions to introduce Python 3 support.
Some of the decisions made in regards to unicode and byte utilization on Python 3 changed how unicode and bytes are handled, which complicates how low
Python 3 make it hard to write low level code. This mainly affects WSGI level code handles HTTP data. This mainly affects WSGI middleware interacting
middlewares and interacting with the WSGI provided information. Werkzeug with the WSGI ``environ`` data. Werkzeug wraps that information in high-level
wraps all that information in high-level helpers but some of those were helpers, so encoding issues should not affect you.
specifically added for the Python 3 support and are quite new.
Unless you require absolute compatibility, you should be fine with Python 3 The majority of the upgrade work is in the lower-level libraries like
nowadays. Most libraries and Flask extensions have been ported by now and Flask and Werkzeug, not the high-level application code.
using Flask with Python 3 is generally a smooth ride. However, keep in mind For example, all of the examples in the Flask repository work on both Python 2 and 3
that most libraries (including Werkzeug and Flask) might not be quite as and did not require a single line of code changed.
stable on Python 3 yet. You might therefore sometimes run into bugs that are
usually encoding-related.
The majority of the upgrade pain is in the lower-level libraries like
Flask and Werkzeug and not in the actual high-level application code. For
instance all of the Flask examples that are in the Flask repository work
out of the box on both 2.x and 3.x and did not require a single line of
code changed.

131
docs/quickstart.rst

@ -26,7 +26,7 @@ So what did that code do?
class will be our WSGI application. class will be our WSGI application.
2. Next we create an instance of this class. The first argument is the name of 2. Next we create an instance of this class. The first argument is the name of
the application's module or package. If you are using a single module (as the application's module or package. If you are using a single module (as
in this example), you should use `__name__` because depending on if it's in this example), you should use ``__name__`` because depending on if it's
started as application or imported as module the name will be different started as application or imported as module the name will be different
(``'__main__'`` versus the actual import name). This is needed so that (``'__main__'`` versus the actual import name). This is needed so that
Flask knows where to look for templates, static files, and so on. For more Flask knows where to look for templates, static files, and so on. For more
@ -42,17 +42,17 @@ your application :file:`flask.py` because this would conflict with Flask
itself. itself.
To run the application you can either use the :command:`flask` command or To run the application you can either use the :command:`flask` command or
python's :option:`-m` switch with Flask. Before you can do that you need python's ``-m`` switch with Flask. Before you can do that you need
to tell your terminal the application to work with by exporting the to tell your terminal the application to work with by exporting the
`FLASK_APP` environment variable:: ``FLASK_APP`` environment variable::
$ export FLASK_APP=hello.py $ export FLASK_APP=hello.py
$ flask run $ flask run
* Running on http://127.0.0.1:5000/ * Running on http://127.0.0.1:5000/
If you are on Windows you need to use `set` instead of `export`. If you are on Windows you need to use ``set`` instead of ``export``.
Alternatively you can use `python -m flask`:: Alternatively you can use :command:`python -m flask`::
$ export FLASK_APP=hello.py $ export FLASK_APP=hello.py
$ python -m flask run $ python -m flask run
@ -86,7 +86,7 @@ should see your hello world greeting.
What to do if the Server does not Start What to do if the Server does not Start
--------------------------------------- ---------------------------------------
In case the ``python -m flask`` fails or :command:`flask` does not exist, In case the :command:`python -m flask` fails or :command:`flask` does not exist,
there are multiple reasons this might be the case. First of all you need there are multiple reasons this might be the case. First of all you need
to look at the error message. to look at the error message.
@ -95,17 +95,17 @@ Old Version of Flask
Versions of Flask older than 0.11 use to have different ways to start the Versions of Flask older than 0.11 use to have different ways to start the
application. In short, the :command:`flask` command did not exist, and application. In short, the :command:`flask` command did not exist, and
neither did ``python -m flask``. In that case you have two options: neither did :command:`python -m flask`. In that case you have two options:
either upgrade to newer Flask versions or have a look at the :ref:`server` either upgrade to newer Flask versions or have a look at the :ref:`server`
docs to see the alternative method for running a server. docs to see the alternative method for running a server.
Invalid Import Name Invalid Import Name
``````````````````` ```````````````````
The :option:`-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.
@ -123,13 +123,13 @@ That is not very nice and Flask can do better. If you enable debug
support the server will reload itself on code changes, and it will also support the server will reload itself on code changes, and it will also
provide you with a helpful debugger if things go wrong. provide you with a helpful debugger if things go wrong.
To enable debug mode you can export the `FLASK_DEBUG` environment variable To enable debug mode you can export the ``FLASK_DEBUG`` environment variable
before running the server:: before running the server::
$ export FLASK_DEBUG=1 $ export FLASK_DEBUG=1
$ flask run $ flask run
(On Windows you need to use `set` instead of `export`). (On Windows you need to use ``set`` instead of ``export``).
This does the following things: This does the following things:
@ -202,7 +202,7 @@ The following converters exist:
=========== =============================================== =========== ===============================================
`string` accepts any text without a slash (the default) `string` accepts any text without a slash (the default)
`int` accepts integers `int` accepts integers
`float` like `int` but for floating point values `float` like ``int`` but for floating point values
`path` like the default but also accepts slashes `path` like the default but also accepts slashes
`any` matches one of the items provided `any` matches one of the items provided
`uuid` accepts UUID strings `uuid` accepts UUID strings
@ -226,7 +226,7 @@ The following converters exist:
Though they look rather similar, they differ in their use of the trailing Though they look rather similar, they differ in their use of the trailing
slash in the URL *definition*. In the first case, the canonical URL for the slash in the URL *definition*. In the first case, the canonical URL for the
`projects` endpoint has a trailing slash. In that sense, it is similar to ``projects`` endpoint has a trailing slash. In that sense, it is similar to
a folder on a filesystem. Accessing it without a trailing slash will cause a folder on a filesystem. Accessing it without a trailing slash will cause
Flask to redirect to the canonical URL with the trailing slash. Flask to redirect to the canonical URL with the trailing slash.
@ -250,29 +250,29 @@ build a URL to a specific function you can use the :func:`~flask.url_for`
function. It accepts the name of the function as first argument and a number function. It accepts the name of the function as first argument and a number
of keyword arguments, each corresponding to the variable part of the URL rule. of keyword arguments, each corresponding to the variable part of the URL rule.
Unknown variable parts are appended to the URL as query parameters. Here are Unknown variable parts are appended to the URL as query parameters. Here are
some examples: some examples::
>>> from flask import Flask, url_for >>> from flask import Flask, url_for
>>> app = Flask(__name__) >>> app = Flask(__name__)
>>> @app.route('/') >>> @app.route('/')
... def index(): pass ... def index(): pass
... ...
>>> @app.route('/login') >>> @app.route('/login')
... def login(): pass ... def login(): pass
... ...
>>> @app.route('/user/<username>') >>> @app.route('/user/<username>')
... 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
/login?next=/ /login?next=/
/user/John%20Doe /user/John%20Doe
(This also uses the :meth:`~flask.Flask.test_request_context` method, explained (This also uses the :meth:`~flask.Flask.test_request_context` method, explained
below. It tells Flask to behave as though it is handling a request, even below. It tells Flask to behave as though it is handling a request, even
@ -288,8 +288,8 @@ There are three good reasons for this:
remember to change URLs all over the place. remember to change URLs all over the place.
2. URL building will handle escaping of special characters and Unicode 2. URL building will handle escaping of special characters and Unicode
data transparently for you, so you don't have to deal with them. data transparently for you, so you don't have to deal with them.
3. If your application is placed outside the URL root (say, in 3. If your application is placed outside the URL root - say, in
``/myapplication`` instead of ``/``), :func:`~flask.url_for` will handle ``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle
that properly for you. that properly for you.
@ -298,7 +298,7 @@ HTTP Methods
HTTP (the protocol web applications are speaking) knows different methods for HTTP (the protocol web applications are speaking) knows different methods for
accessing URLs. By default, a route only answers to ``GET`` requests, but that accessing URLs. By default, a route only answers to ``GET`` requests, but that
can be changed by providing the `methods` argument to the can be changed by providing the ``methods`` argument to the
:meth:`~flask.Flask.route` decorator. Here are some examples:: :meth:`~flask.Flask.route` decorator. Here are some examples::
from flask import request from flask import request
@ -446,22 +446,22 @@ know how that works, head over to the :ref:`template-inheritance` pattern
documentation. Basically template inheritance makes it possible to keep documentation. Basically template inheritance makes it possible to keep
certain elements on each page (like header, navigation and footer). certain elements on each page (like header, navigation and footer).
Automatic escaping is enabled, so if `name` contains HTML it will be escaped Automatic escaping is enabled, so if ``name`` contains HTML it will be escaped
automatically. If you can trust a variable and you know that it will be automatically. If you can trust a variable and you know that it will be
safe HTML (for example because it came from a module that converts wiki safe HTML (for example because it came from a module that converts wiki
markup to HTML) you can mark it as safe by using the markup to HTML) you can mark it as safe by using the
:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the :class:`~jinja2.Markup` class or by using the ``|safe`` filter in the
template. Head over to the Jinja 2 documentation for more examples. template. Head over to the Jinja 2 documentation for more examples.
Here is a basic introduction to how the :class:`~jinja2.Markup` class works: Here is a basic introduction to how the :class:`~jinja2.Markup` class works::
>>> from flask import Markup >>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>' >>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>') Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>') >>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;') Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags() >>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up \xbb HTML' u'Marked up \xbb HTML'
.. versionchanged:: 0.5 .. versionchanged:: 0.5
@ -540,7 +540,7 @@ 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
@ -563,7 +563,7 @@ attributes mentioned above::
# was GET or the credentials were invalid # was GET or the credentials were invalid
return render_template('login.html', error=error) return render_template('login.html', error=error)
What happens if the key does not exist in the `form` attribute? In that What happens if the key does not exist in the ``form`` attribute? In that
case a special :exc:`KeyError` is raised. You can catch it like a case a special :exc:`KeyError` is raised. You can catch it like a
standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request
error page is shown instead. So for many situations you don't have to error page is shown instead. So for many situations you don't have to
@ -725,17 +725,15 @@ converting return values into response objects is as follows:
3. If a tuple is returned the items in the tuple can provide extra 3. If a tuple is returned the items in the tuple can provide extra
information. Such tuples have to be in the form ``(response, status, information. Such tuples have to be in the form ``(response, status,
headers)`` or ``(response, headers)`` where at least one item has headers)`` or ``(response, headers)`` where at least one item has
to be in the tuple. The `status` value will override the status code to be in the tuple. The ``status`` value will override the status code
and `headers` can be a list or dictionary of additional header values. and ``headers`` can be a list or dictionary of additional header values.
4. If none of that works, Flask will assume the return value is a 4. If none of that works, Flask will assume the return value is a
valid WSGI application and convert that into a response object. valid WSGI application and convert that into a response object.
If you want to get hold of the resulting response object inside the view If you want to get hold of the resulting response object inside the view
you can use the :func:`~flask.make_response` function. you can use the :func:`~flask.make_response` function.
Imagine you have a view like this: Imagine you have a view like this::
.. sourcecode:: python
@app.errorhandler(404) @app.errorhandler(404)
def not_found(error): def not_found(error):
@ -743,9 +741,7 @@ Imagine you have a view like this:
You just need to wrap the return expression with You just need to wrap the return expression with
:func:`~flask.make_response` and get the response object to modify it, then :func:`~flask.make_response` and get the response object to modify it, then
return it: return it::
.. sourcecode:: python
@app.errorhandler(404) @app.errorhandler(404)
def not_found(error): def not_found(error):
@ -784,7 +780,7 @@ sessions work::
session['username'] = request.form['username'] session['username'] = request.form['username']
return redirect(url_for('index')) return redirect(url_for('index'))
return ''' return '''
<form action="" method="post"> <form method="post">
<p><input type=text name=username> <p><input type=text name=username>
<p><input type=submit value=Login> <p><input type=submit value=Login>
</form> </form>
@ -807,13 +803,13 @@ not using the template engine (as in this example).
The problem with random is that it's hard to judge what is truly random. And The problem with random is that it's hard to judge what is truly random. And
a secret key should be as random as possible. Your operating system a secret key should be as random as possible. Your operating system
has ways to generate pretty random stuff based on a cryptographic has ways to generate pretty random stuff based on a cryptographic
random generator which can be used to get such a key: random generator which can be used to get such a key::
>>> import os >>> import os
>>> os.urandom(24) >>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8' '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
Just take that thing and copy/paste it into your code and you're done. Just take that thing and copy/paste it into your code and you're done.
A note on cookie-based sessions: Flask will take the values you put into the A note on cookie-based sessions: Flask will take the values you put into the
session object and serialize them into a cookie. If you are finding some session object and serialize them into a cookie. If you are finding some
@ -821,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
---------------- ----------------

14
docs/testing.rst

@ -152,13 +152,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 +172,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 '&lt;Hello&gt;' in rv.data assert b'&lt;Hello&gt;' 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.

2
docs/tutorial/dbinit.rst

@ -35,7 +35,7 @@ just 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

2
docs/tutorial/folders.rst

@ -19,7 +19,7 @@ creating the database schema as well as the main module.
As a quick side note, the files inside of the :file:`static` folder are As a quick side note, the files inside of the :file:`static` folder are
available to users of the application via HTTP. This is the place where CSS and available to users of the application via HTTP. This is the place where CSS and
Javascript files go. Inside the :file:`templates` folder, Flask will look for JavaScript files go. Inside the :file:`templates` folder, Flask will look for
`Jinja2`_ templates. You will see examples of this later on. `Jinja2`_ templates. You will see examples of this later on.
For now you should continue with :ref:`tutorial-schema`. For now you should continue with :ref:`tutorial-schema`.

2
docs/tutorial/packaging.rst

@ -55,7 +55,7 @@ into this file, :file:`flaskr/__init__.py`:
.. sourcecode:: python .. sourcecode:: python
from flaskr import app from .flaskr import app
This import statement brings the application instance into the top-level This import statement brings the application instance into the top-level
of the application package. When it is time to run the application, the of the application package. When it is time to run the application, the

62
docs/upgrading.rst

@ -14,12 +14,50 @@ This section of the documentation enumerates all the changes in Flask from
release to release and how you can change your code to have a painless release to release and how you can change your code to have a painless
updating experience. updating experience.
If you want to use the :command:`easy_install` command to upgrade your Flask Use the :command:`pip` command to upgrade your existing Flask installation by
installation, make sure to pass it the :option:`-U` parameter:: providing the ``--upgrade`` parameter::
$ easy_install -U 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 a 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
------------ ------------
@ -30,7 +68,7 @@ to the release we decided to push out a 0.11 release first with some
changes removed to make the transition easier. If you have been tracking changes removed to make the transition easier. If you have been tracking
the master branch which was 1.0 you might see some unexpected changes. the master branch which was 1.0 you might see some unexpected changes.
In case you did track the master branch you will notice that `flask --app` In case you did track the master branch you will notice that :command:`flask --app`
is removed now. You need to use the environment variable to specify an is removed now. You need to use the environment variable to specify an
application. application.
@ -68,7 +106,7 @@ Templating
The :func:`~flask.templating.render_template_string` function has changed to The :func:`~flask.templating.render_template_string` function has changed to
autoescape template variables by default. This better matches the behavior autoescape template variables by default. This better matches the behavior
of :func:`~flask.templating.render_template`. of :func:`~flask.templating.render_template`.
Extension imports Extension imports
````````````````` `````````````````
@ -133,7 +171,7 @@ Version 0.8
----------- -----------
Flask introduced a new session interface system. We also noticed that Flask introduced a new session interface system. We also noticed that
there was a naming collision between `flask.session` the module that there was a naming collision between ``flask.session`` the module that
implements sessions and :data:`flask.session` which is the global session implements sessions and :data:`flask.session` which is the global session
object. With that introduction we moved the implementation details for object. With that introduction we moved the implementation details for
the session system into a new module called :mod:`flask.sessions`. If you the session system into a new module called :mod:`flask.sessions`. If you
@ -199,7 +237,7 @@ Please note that deprecation warnings are disabled by default starting
with Python 2.7. In order to see the deprecation warnings that might be with Python 2.7. In order to see the deprecation warnings that might be
emitted you have to enabled them with the :mod:`warnings` module. emitted you have to enabled them with the :mod:`warnings` module.
If you are working with windows and you lack the `patch` command line If you are working with windows and you lack the ``patch`` command line
utility you can get it as part of various Unix runtime environments for utility you can get it as part of various Unix runtime environments for
windows including cygwin, msysgit or ming32. Also source control systems windows including cygwin, msysgit or ming32. Also source control systems
like svn, hg or git have builtin support for applying unified diffs as like svn, hg or git have builtin support for applying unified diffs as
@ -316,7 +354,7 @@ to upgrade. What changed?
runtime. runtime.
- Blueprints have an inverse behavior for :meth:`url_for`. Previously - Blueprints have an inverse behavior for :meth:`url_for`. Previously
``.foo`` told :meth:`url_for` that it should look for the endpoint ``.foo`` told :meth:`url_for` that it should look for the endpoint
`foo` on the application. Now it means “relative to current module”. ``foo`` on the application. Now it means “relative to current module”.
The script will inverse all calls to :meth:`url_for` automatically for The script will inverse all calls to :meth:`url_for` automatically for
you. It will do this in a very eager way so you might end up with you. It will do this in a very eager way so you might end up with
some unnecessary leading dots in your code if you're not using some unnecessary leading dots in your code if you're not using
@ -334,7 +372,7 @@ to upgrade. What changed?
name into that folder if you want :file:`blueprintname/template.html` as name into that folder if you want :file:`blueprintname/template.html` as
the template name. the template name.
If you continue to use the `Module` object which is deprecated, Flask will If you continue to use the ``Module`` object which is deprecated, Flask will
restore the previous behavior as good as possible. However we strongly restore the previous behavior as good as possible. However we strongly
recommend upgrading to the new blueprints as they provide a lot of useful recommend upgrading to the new blueprints as they provide a lot of useful
improvement such as the ability to attach a blueprint multiple times, improvement such as the ability to attach a blueprint multiple times,
@ -354,7 +392,7 @@ change the order.
Another change that breaks backwards compatibility is that context Another change that breaks backwards compatibility is that context
processors will no longer override values passed directly to the template processors will no longer override values passed directly to the template
rendering function. If for example `request` is as variable passed rendering function. If for example ``request`` is as variable passed
directly to the template, the default context processor will not override directly to the template, the default context processor will not override
it with the current request object. This makes it easier to extend it with the current request object. This makes it easier to extend
context processors later to inject additional variables without breaking context processors later to inject additional variables without breaking
@ -380,7 +418,7 @@ The following changes may be relevant to your application:
for this feature. Removing support for this makes the Flask internal for this feature. Removing support for this makes the Flask internal
code easier to understand and fixes a couple of small issues that make code easier to understand and fixes a couple of small issues that make
debugging harder than necessary. debugging harder than necessary.
- The `create_jinja_loader` function is gone. If you want to customize - The ``create_jinja_loader`` function is gone. If you want to customize
the Jinja loader now, use the the Jinja loader now, use the
:meth:`~flask.Flask.create_jinja_environment` method instead. :meth:`~flask.Flask.create_jinja_environment` method instead.

2
examples/flaskr/flaskr/__init__.py

@ -1 +1 @@
from flaskr import app from .flaskr import app

4
examples/flaskr/flaskr/templates/show_entries.html

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

@ -1,2 +1,2 @@
[aliases] [tool:pytest]
test=pytest test=pytest

2
examples/minitwit/minitwit/__init__.py

@ -1 +1 @@
from minitwit import app from .minitwit import app

2
flask/__init__.py

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

78
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
@ -519,7 +518,7 @@ 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__)
@ -838,12 +837,11 @@ class Flask(_PackageBoundObject):
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 +875,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 ....')
@ -935,22 +933,7 @@ class Flask(_PackageBoundObject):
@setupmethod @setupmethod
def register_blueprint(self, blueprint, **options): def register_blueprint(self, blueprint, **options):
"""Register a blueprint on the application. For information about """Registers a blueprint on the application.
blueprints head over to :ref:`blueprints`.
The blueprint name is passed in as the first argument.
Options are passed as additional keyword arguments and forwarded to
`blueprints` in an "options" dictionary.
:param subdomain: set a subdomain for the blueprint
:param url_prefix: set the prefix for all URLs defined on the blueprint.
``(url_prefix='/<lang code>')``
:param url_defaults: a dictionary with URL defaults that is added to
each and every URL defined with this blueprint
:param static_folder: add a static folder to urls in this blueprint
:param static_url_path: add a static url path to urls in this blueprint
:param template_folder: set an alternate template folder
:param root_path: set an alternate root path for this blueprint
.. versionadded:: 0.7 .. versionadded:: 0.7
""" """
@ -1131,7 +1114,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)
@ -1169,7 +1152,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)
@ -1452,24 +1436,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, {})
@ -1571,7 +1544,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`
@ -1639,9 +1612,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):
@ -1819,7 +1813,7 @@ class Flask(_PackageBoundObject):
if it was the return value from the view and further if it was the return value from the view and further
request handling is stopped. request handling is stopped.
This also triggers the :meth:`url_value_processor` functions before This also triggers the :meth:`url_value_preprocessor` functions before
the actual :meth:`before_request` functions are called. the actual :meth:`before_request` functions are called.
""" """
bp = _request_ctx_stack.top.request.blueprint bp = _request_ctx_stack.top.request.blueprint
@ -1988,7 +1982,7 @@ 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)
return response(environ, start_response) return response(environ, start_response)
finally: finally:
if self.should_ignore_error(error): if self.should_ignore_error(error):

47
flask/cli.py

@ -18,7 +18,7 @@ import click
from ._compat import iteritems, reraise from ._compat import iteritems, reraise
from .helpers import get_debug_flag from .helpers import get_debug_flag
from . import __version__
class NoAppException(click.UsageError): class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded.""" """Raised if an application cannot be found or loaded."""
@ -86,7 +86,13 @@ def locate_app(app_id):
module = app_id module = app_id
app_obj = None app_obj = None
__import__(module) try:
__import__(module)
except ImportError:
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)
@ -108,10 +114,26 @@ def find_default_import_path():
return app return app
def get_version(ctx, param, value):
if not value or ctx.resilient_parsing:
return
message = 'Flask %(version)s\nPython %(python_version)s'
click.echo(message % {
'version': __version__,
'python_version': sys.version,
}, color=ctx.color)
ctx.exit()
version_option = click.Option(['--version'],
help='Show the flask version',
expose_value=False,
callback=get_version,
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.
""" """
@ -270,12 +292,19 @@ class FlaskGroup(AppGroup):
:param add_default_commands: if this is True then the default run and :param add_default_commands: if this is True then the default run and
shell commands wil be added. shell commands wil be added.
:param add_version_option: adds the ``--version`` option.
:param create_app: an optional callback that is passed the script info :param create_app: an optional callback that is passed the script info
and returns the loaded app. and returns the loaded app.
""" """
def __init__(self, add_default_commands=True, create_app=None, **extra): def __init__(self, add_default_commands=True, create_app=None,
AppGroup.__init__(self, **extra) add_version_option=True, **extra):
params = list(extra.pop('params', None) or ())
if add_version_option:
params.append(version_option)
AppGroup.__init__(self, params=params, **extra)
self.create_app = create_app self.create_app = create_app
if add_default_commands: if add_default_commands:
@ -387,7 +416,7 @@ def run_command(info, host, port, reload, debugger, eager_loading,
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
# Extra startup messages. This depends a but on Werkzeug internals to # Extra startup messages. This depends a bit on Werkzeug internals to
# not double execute when the reloader kicks in. # not double execute when the reloader kicks in.
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
# If we have an import path we can print it out now which can help # If we have an import path we can print it out now which can help
@ -440,7 +469,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.
@ -449,7 +478,7 @@ The most useful commands are the "run" and "shell" command.
Example usage: Example usage:
\b \b
%(prefix)s%(cmd)s FLASK_APP=hello %(prefix)s%(cmd)s FLASK_APP=hello.py
%(prefix)s%(cmd)s FLASK_DEBUG=1 %(prefix)s%(cmd)s FLASK_DEBUG=1
%(prefix)sflask run %(prefix)sflask run
""" % { """ % {

11
flask/config.py

@ -143,10 +143,12 @@ class Config(dict):
- a string: in this case the object with that name will be imported - a string: in this case the object with that name will be imported
- an actual object reference: that object is used directly - an actual object reference: that object is used directly
Objects are usually either modules or classes. Objects are usually either modules or classes. :meth:`from_object`
loads only the uppercase attributes of the module/class. A ``dict``
object will not work with :meth:`from_object` because the keys of a
``dict`` are not attributes of the ``dict`` class.
Just the uppercase variables in that object are stored in the config. Example of module-based configuration::
Example usage::
app.config.from_object('yourapplication.default_config') app.config.from_object('yourapplication.default_config')
from yourapplication import default_config from yourapplication import default_config
@ -157,6 +159,9 @@ class Config(dict):
with :meth:`from_pyfile` and ideally from a location not within the with :meth:`from_pyfile` and ideally from a location not within the
package because the package might be installed system wide. package because the package might be installed system wide.
See :ref:`config-dev-prod` for an example of class-based configuration
using :meth:`from_object`.
:param obj: an import name or object :param obj: an import name or object
""" """
if isinstance(obj, string_types): if isinstance(obj, string_types):

2
flask/exthook.py

@ -68,7 +68,7 @@ class ExtensionImporter(object):
warnings.warn( warnings.warn(
"Importing flask.ext.{x} is deprecated, use flask_{x} instead." "Importing flask.ext.{x} is deprecated, use flask_{x} instead."
.format(x=modname), ExtDeprecationWarning .format(x=modname), ExtDeprecationWarning, stacklevel=2
) )
for path in self.module_choices: for path in self.module_choices:

146
flask/helpers.py

@ -25,8 +25,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:
@ -429,7 +430,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]):
def send_file(filename_or_fp, mimetype=None, as_attachment=False, def send_file(filename_or_fp, mimetype=None, as_attachment=False,
attachment_filename=None, add_etags=True, attachment_filename=None, add_etags=True,
cache_timeout=None, conditional=False): cache_timeout=None, conditional=False, last_modified=None):
"""Sends the contents of a file to the client. This will use the """Sends the contents of a file to the client. This will use the
most efficient method available and configured. By default it will most efficient method available and configured. By default it will
try to use the WSGI server's file_wrapper support. Alternatively try to use the WSGI server's file_wrapper support. Alternatively
@ -443,6 +444,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
guessing requires a `filename` or an `attachment_filename` to be guessing requires a `filename` or an `attachment_filename` to be
provided. 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.
@ -461,6 +469,15 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
.. versionchanged:: 0.9 .. versionchanged:: 0.9
cache_timeout pulls its default from application config, when None. cache_timeout pulls its default from application config, when None.
.. versionchanged:: 0.12
The filename is no longer automatically inferred from file objects. If
you want to use automatic mimetype and etag support, pass a filepath via
`filename_or_fp` or `attachment_filename`.
.. versionchanged:: 0.12
The `attachment_filename` is preferred over `filename` for MIME-type
detection.
:param filename_or_fp: the filename of the file to send in `latin-1`. :param filename_or_fp: the filename of the file to send in `latin-1`.
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.
@ -469,8 +486,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
@ -482,46 +500,40 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
(default), this value is set by (default), this value is set by
:meth:`~Flask.get_send_file_max_age` of :meth:`~Flask.get_send_file_max_age` of
:data:`~flask.current_app`. :data:`~flask.current_app`.
:param last_modified: set the ``Last-Modified`` header to this value,
a :class:`~datetime.datetime` or timestamp.
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
if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename)
file = None file = None
if attachment_filename is None:
attachment_filename = os.path.basename(filename)
else: else:
from warnings import warn
file = filename_or_fp file = filename_or_fp
filename = getattr(file, 'name', None) filename = None
# XXX: this behavior is now deprecated because it was unreliable.
# removed in Flask 1.0
if not attachment_filename and not mimetype \
and isinstance(filename, string_types):
warn(DeprecationWarning('The filename support for file objects '
'passed to send_file is now deprecated. Pass an '
'attach_filename if you want mimetypes to be guessed.'),
stacklevel=2)
if add_etags:
warn(DeprecationWarning('In future flask releases etags will no '
'longer be generated for file objects passed to the send_file '
'function because this behavior was unreliable. Pass '
'filenames instead if possible, otherwise attach an etag '
'yourself based on another value'), stacklevel=2)
if filename is not None:
if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename)
if mimetype is None and (filename or attachment_filename):
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
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)
headers.add('Content-Disposition', 'attachment', headers.add('Content-Disposition', 'attachment',
filename=attachment_filename) filename=attachment_filename)
@ -529,22 +541,24 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
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,
direct_passthrough=True) direct_passthrough=True)
# if we know the file modification date, we can store it as if last_modified is not None:
# the time of the last modification. rv.last_modified = last_modified
if mtime is not None: elif mtime is not None:
rv.last_modified = int(mtime) rv.last_modified = mtime
rv.cache_control.public = True rv.cache_control.public = True
if cache_timeout is None: if cache_timeout is None:
@ -554,6 +568,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
rv.expires = int(time() + cache_timeout) rv.expires = int(time() + cache_timeout)
if add_etags and filename is not None: if add_etags and filename is not None:
from warnings import warn
try: try:
rv.set_etag('%s-%s-%s' % ( rv.set_etag('%s-%s-%s' % (
os.path.getmtime(filename), os.path.getmtime(filename),
@ -567,17 +583,28 @@ 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
def safe_join(directory, filename): def safe_join(directory, *pathnames):
"""Safely join `directory` and `filename`. """Safely join `directory` and zero or more untrusted `pathnames`
components.
Example usage:: Example usage::
@ -587,20 +614,23 @@ def safe_join(directory, filename):
with open(filename, 'rb') as fd: with open(filename, 'rb') as fd:
content = fd.read() # Read and process the file content... content = fd.read() # Read and process the file content...
:param directory: the base directory. :param directory: the trusted base directory.
:param filename: the untrusted filename relative to that directory. :param pathnames: the untrusted pathnames relative to that directory.
:raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path :raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
would fall out of `directory`. paths fall out of its boundaries.
""" """
filename = posixpath.normpath(filename) for filename in pathnames:
for sep in _os_alt_seps: if filename != '':
if sep in filename: filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
raise NotFound() raise NotFound()
if os.path.isabs(filename) or \ directory = os.path.join(directory, filename)
filename == '..' or \ return directory
filename.startswith('../'):
raise NotFound()
return os.path.join(directory, filename)
def send_from_directory(directory, filename, **options): def send_from_directory(directory, filename, **options):

5
flask/json.py

@ -19,10 +19,7 @@ from jinja2 import Markup
# Use the same json implementation as itsdangerous on which we # Use the same json implementation as itsdangerous on which we
# depend anyways. # depend anyways.
try: from itsdangerous import json as _json
from itsdangerous import simplejson as _json
except ImportError:
from itsdangerous import json as _json
# Figure out if simplejson escapes slashes. This behavior was changed # Figure out if simplejson escapes slashes. This behavior was changed

4
flask/logging.py

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

2
flask/sessions.py

@ -168,7 +168,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

2
flask/signals.py

@ -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()

14
flask/testing.py

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

2
flask/views.py

@ -123,7 +123,7 @@ class MethodViewType(type):
class MethodView(with_metaclass(MethodViewType, View)): class MethodView(with_metaclass(MethodViewType, View)):
"""Like a regular class-based view but that dispatches requests to """Like a regular class-based view but that dispatches requests to
particular methods. For instance if you implement a method called particular methods. For instance if you implement a method called
:meth:`get` it means you will response to ``'GET'`` requests and :meth:`get` it means it will respond to ``'GET'`` requests and
the :meth:`dispatch_request` implementation will automatically the :meth:`dispatch_request` implementation will automatically
forward your request to that. Also :attr:`options` is set for you forward your request to that. Also :attr:`options` is set for you
automatically:: automatically::

3
scripts/flask-07-upgrade.py

@ -19,6 +19,7 @@
:copyright: (c) Copyright 2015 by Armin Ronacher. :copyright: (c) Copyright 2015 by Armin Ronacher.
:license: see LICENSE for more details. :license: see LICENSE for more details.
""" """
from __future__ import print_function
import re import re
import os import os
import inspect import inspect
@ -59,7 +60,7 @@ def make_diff(filename, old, new):
posixpath.normpath(posixpath.join('a', filename)), posixpath.normpath(posixpath.join('a', filename)),
posixpath.normpath(posixpath.join('b', filename)), posixpath.normpath(posixpath.join('b', filename)),
lineterm=''): lineterm=''):
print line print(line)
def looks_like_teardown_function(node): def looks_like_teardown_function(node):

5
scripts/make-release.py

@ -10,6 +10,7 @@
: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.
""" """
from __future__ import print_function
import sys import sys
import os import os
import re import re
@ -85,12 +86,12 @@ def build_and_upload():
def fail(message, *args): def fail(message, *args):
print >> sys.stderr, 'Error:', message % args print('Error:', message % args, file=sys.stderr)
sys.exit(1) sys.exit(1)
def info(message, *args): def info(message, *args):
print >> sys.stderr, message % args print(message % args, file=sys.stderr)
def get_git_tags(): def get_git_tags():

7
setup.cfg

@ -4,5 +4,8 @@ release = egg_info -RDb ''
[wheel] [wheel]
universal = 1 universal = 1
[pytest] [metadata]
norecursedirs = .* *.egg *.egg-info env* artwork docs examples license_file = LICENSE
[tool:pytest]
norecursedirs = .* *.egg *.egg-info env* artwork docs

2
setup.py

@ -33,6 +33,8 @@ And run it:
$ python hello.py $ python hello.py
* Running on http://localhost:5000/ * Running on http://localhost:5000/
Ready for production? `Read this first <http://flask.pocoo.org/docs/deploying/>`.
Links Links
````` `````

1
test-requirements.txt

@ -0,0 +1 @@
tox

11
tests/conftest.py

@ -7,6 +7,7 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import flask import flask
import gc
import os import os
import sys import sys
import pkgutil import pkgutil
@ -126,8 +127,8 @@ def purge_module(request):
return inner return inner
@pytest.fixture @pytest.yield_fixture(autouse=True)
def catch_deprecation_warnings(): def catch_deprecation_warnings(recwarn):
import warnings yield
warnings.simplefilter('default', category=DeprecationWarning) gc.collect()
return lambda: warnings.catch_warnings(record=True) assert not recwarn.list

57
tests/test_basic.py

@ -768,6 +768,29 @@ def test_error_handling():
assert b'forbidden' == rv.data assert b'forbidden' == rv.data
def test_error_handling_processing():
app = flask.Flask(__name__)
app.config['LOGGER_HANDLER_POLICY'] = 'never'
@app.errorhandler(500)
def internal_server_error(e):
return 'internal server error', 500
@app.route('/')
def broken_func():
1 // 0
@app.after_request
def after_request(resp):
resp.mimetype = 'text/x-special'
return resp
with app.test_client() as c:
resp = c.get('/')
assert resp.mimetype == 'text/x-special'
assert resp.data == b'internal server error'
def test_before_request_and_routing_errors(): def test_before_request_and_routing_errors():
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -1031,6 +1054,14 @@ def test_jsonify_mimetype():
assert rv.mimetype == 'application/vnd.api+json' assert rv.mimetype == 'application/vnd.api+json'
def test_jsonify_args_and_kwargs_check():
app = flask.Flask(__name__)
with app.test_request_context():
with pytest.raises(TypeError) as e:
flask.jsonify('fake args', kwargs='fake')
assert 'behavior undefined' in str(e.value)
def test_url_generation(): def test_url_generation():
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -1116,6 +1147,30 @@ def test_static_files():
rv.close() rv.close()
def test_static_path_deprecated(recwarn):
app = flask.Flask(__name__, static_path='/foo')
recwarn.pop(DeprecationWarning)
app.testing = True
rv = app.test_client().get('/foo/index.html')
assert rv.status_code == 200
rv.close()
with app.test_request_context():
assert flask.url_for('static', filename='index.html') == '/foo/index.html'
def test_static_url_path():
app = flask.Flask(__name__, static_url_path='/foo')
app.testing = True
rv = app.test_client().get('/foo/index.html')
assert rv.status_code == 200
rv.close()
with app.test_request_context():
assert flask.url_for('static', filename='index.html') == '/foo/index.html'
def test_none_response(): def test_none_response():
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.testing = True app.testing = True
@ -1236,8 +1291,6 @@ def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger,
monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock)
app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions
app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader)
# make sure werkzeug always passes errors through
assert rv['passthrough_errors']
def test_max_content_length(): def test_max_content_length():

79
tests/test_cli.py

@ -12,6 +12,8 @@
# Copyright (C) 2015 CERN. # Copyright (C) 2015 CERN.
# #
from __future__ import absolute_import, print_function from __future__ import absolute_import, print_function
import os
import sys
import click import click
import pytest import pytest
@ -19,7 +21,8 @@ from click.testing import CliRunner
from flask import Flask, current_app from flask import Flask, current_app
from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \
find_best_app, locate_app, with_appcontext, prepare_exec_for_file find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \
find_default_import_path, get_version
def test_cli_name(test_apps): def test_cli_name(test_apps):
@ -29,31 +32,47 @@ def test_cli_name(test_apps):
def test_find_best_app(test_apps): def test_find_best_app(test_apps):
"""Test of find_best_app.""" """Test if `find_best_app` behaves as expected with different combinations of input."""
class mod: class Module:
app = Flask('appname') app = Flask('appname')
assert find_best_app(mod) == mod.app assert find_best_app(Module) == Module.app
class mod: class Module:
application = Flask('appname') application = Flask('appname')
assert find_best_app(mod) == mod.application assert find_best_app(Module) == Module.application
class mod: class Module:
myapp = Flask('appname') myapp = Flask('appname')
assert find_best_app(mod) == mod.myapp assert find_best_app(Module) == Module.myapp
class mod: class Module:
myapp = Flask('appname') pass
myapp2 = Flask('appname2') pytest.raises(NoAppException, find_best_app, Module)
pytest.raises(NoAppException, find_best_app, mod) class Module:
myapp1 = Flask('appname1')
myapp2 = Flask('appname2')
pytest.raises(NoAppException, find_best_app, Module)
def test_prepare_exec_for_file(test_apps): def test_prepare_exec_for_file(test_apps):
assert prepare_exec_for_file('test.py') == 'test' """Expect the correct path to be set and the correct module name to be returned.
assert prepare_exec_for_file('/usr/share/__init__.py') == 'share'
:func:`prepare_exec_for_file` has a side effect, where
the parent directory of given file is added to `sys.path`.
"""
realpath = os.path.realpath('/tmp/share/test.py')
dirname = os.path.dirname(realpath)
assert prepare_exec_for_file('/tmp/share/test.py') == 'test'
assert dirname in sys.path
realpath = os.path.realpath('/tmp/share/__init__.py')
dirname = os.path.dirname(os.path.dirname(realpath))
assert prepare_exec_for_file('/tmp/share/__init__.py') == 'share'
assert dirname in sys.path
with pytest.raises(NoAppException): with pytest.raises(NoAppException):
prepare_exec_for_file('test.txt') prepare_exec_for_file('/tmp/share/test.txt')
def test_locate_app(test_apps): def test_locate_app(test_apps):
@ -61,9 +80,39 @@ def test_locate_app(test_apps):
assert locate_app("cliapp.app").name == "testapp" assert locate_app("cliapp.app").name == "testapp"
assert locate_app("cliapp.app:testapp").name == "testapp" assert locate_app("cliapp.app:testapp").name == "testapp"
assert locate_app("cliapp.multiapp:app1").name == "app1" assert locate_app("cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app, "notanpp.py")
pytest.raises(NoAppException, locate_app, "cliapp/app")
pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp")
def test_find_default_import_path(test_apps, monkeypatch, tmpdir):
"""Test of find_default_import_path."""
monkeypatch.delitem(os.environ, 'FLASK_APP', raising=False)
assert find_default_import_path() == None
monkeypatch.setitem(os.environ, 'FLASK_APP', 'notanapp')
assert find_default_import_path() == 'notanapp'
tmpfile = tmpdir.join('testapp.py')
tmpfile.write('')
monkeypatch.setitem(os.environ, 'FLASK_APP', str(tmpfile))
expect_rv = prepare_exec_for_file(str(tmpfile))
assert find_default_import_path() == expect_rv
def test_get_version(test_apps, capsys):
"""Test of get_version."""
from flask import __version__ as flask_ver
from sys import version as py_ver
class MockCtx(object):
resilient_parsing = False
color = None
def exit(self): return
ctx = MockCtx()
get_version(ctx, None, "test")
out, err = capsys.readouterr()
assert flask_ver in out
assert py_ver in out
def test_scriptinfo(test_apps): def test_scriptinfo(test_apps):
"""Test of ScriptInfo.""" """Test of ScriptInfo."""
obj = ScriptInfo(app_import_path="cliapp.app:testapp") obj = ScriptInfo(app_import_path="cliapp.app:testapp")

20
tests/test_deprecations.py

@ -16,7 +16,7 @@ import flask
class TestRequestDeprecation(object): class TestRequestDeprecation(object):
def test_request_json(self, catch_deprecation_warnings): def test_request_json(self, recwarn):
"""Request.json is deprecated""" """Request.json is deprecated"""
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.testing = True app.testing = True
@ -27,13 +27,11 @@ class TestRequestDeprecation(object):
print(flask.request.json) print(flask.request.json)
return 'OK' return 'OK'
with catch_deprecation_warnings() as captured: c = app.test_client()
c = app.test_client() c.post('/', data='{"spam": 42}', content_type='application/json')
c.post('/', data='{"spam": 42}', content_type='application/json') recwarn.pop(DeprecationWarning)
assert len(captured) == 1 def test_request_module(self, recwarn):
def test_request_module(self, catch_deprecation_warnings):
"""Request.module is deprecated""" """Request.module is deprecated"""
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.testing = True app.testing = True
@ -43,8 +41,6 @@ class TestRequestDeprecation(object):
assert flask.request.module is None assert flask.request.module is None
return 'OK' return 'OK'
with catch_deprecation_warnings() as captured: c = app.test_client()
c = app.test_client() c.get('/')
c.get('/') recwarn.pop(DeprecationWarning)
assert len(captured) == 1

12
tests/test_ext.py

@ -20,6 +20,18 @@ except ImportError:
from flask._compat import PY2 from flask._compat import PY2
@pytest.fixture(autouse=True)
def disable_extwarnings(request, recwarn):
from flask.exthook import ExtDeprecationWarning
def inner():
assert set(w.category for w in recwarn.list) \
<= set([ExtDeprecationWarning])
recwarn.clear()
request.addfinalizer(inner)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def importhook_setup(monkeypatch, request): def importhook_setup(monkeypatch, request):
# we clear this out for various reasons. The most important one is # we clear this out for various reasons. The most important one is

300
tests/test_helpers.py

@ -12,10 +12,13 @@
import pytest import pytest
import os import os
import uuid
import datetime import datetime
import flask import flask
from logging import StreamHandler from logging import StreamHandler
from werkzeug.exceptions import BadRequest from werkzeug.datastructures import Range
from werkzeug.exceptions import BadRequest, NotFound
from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import parse_cache_control_header, parse_options_header
from werkzeug.http import http_date from werkzeug.http import http_date
from flask._compat import StringIO, text_type from flask._compat import StringIO, text_type
@ -99,20 +102,28 @@ class TestJSON(object):
rv = flask.json.dumps(u'\N{SNOWMAN}') rv = flask.json.dumps(u'\N{SNOWMAN}')
assert rv == u'"\u2603"' assert rv == u'"\u2603"'
def test_jsonify_basic_types(self): def test_json_dump_to_file(self):
app = flask.Flask(__name__)
test_data = {'name': 'Flask'}
out = StringIO()
with app.app_context():
flask.json.dump(test_data, out)
out.seek(0)
rv = flask.json.load(out)
assert rv == test_data
@pytest.mark.parametrize('test_value', [0, -1, 1, 23, 3.14, 's', "longer string", True, False, None])
def test_jsonify_basic_types(self, test_value):
"""Test jsonify with basic types.""" """Test jsonify with basic types."""
# Should be able to use pytest parametrize on this, but I couldn't
# figure out the correct syntax
# https://pytest.org/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions
test_data = (0, 1, 23, 3.14, 's', "longer string", True, False,)
app = flask.Flask(__name__) app = flask.Flask(__name__)
c = app.test_client() c = app.test_client()
for i, d in enumerate(test_data):
url = '/jsonify_basic_types{0}'.format(i) url = '/jsonify_basic_types'
app.add_url_rule(url, str(i), lambda x=d: flask.jsonify(x)) app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x))
rv = c.get(url) rv = c.get(url)
assert rv.mimetype == 'application/json' assert rv.mimetype == 'application/json'
assert flask.json.loads(rv.data) == d assert flask.json.loads(rv.data) == test_value
def test_jsonify_dicts(self): def test_jsonify_dicts(self):
"""Test jsonify with dicts and kwargs unpacking.""" """Test jsonify with dicts and kwargs unpacking."""
@ -156,12 +167,10 @@ class TestJSON(object):
def test_jsonify_date_types(self): def test_jsonify_date_types(self):
"""Test jsonify with datetime.date and datetime.datetime types.""" """Test jsonify with datetime.date and datetime.datetime types."""
test_dates = ( test_dates = (
datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.datetime(1973, 3, 11, 6, 30, 45),
datetime.date(1975, 1, 5) datetime.date(1975, 1, 5)
) )
app = flask.Flask(__name__) app = flask.Flask(__name__)
c = app.test_client() c = app.test_client()
@ -172,6 +181,22 @@ class TestJSON(object):
assert rv.mimetype == 'application/json' assert rv.mimetype == 'application/json'
assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple())
def test_jsonify_uuid_types(self):
"""Test jsonify with uuid.UUID types"""
test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF' * 4)
app = flask.Flask(__name__)
url = '/uuid_test'
app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid))
c = app.test_client()
rv = c.get(url)
rv_x = flask.json.loads(rv.data)['x']
assert rv_x == str(test_uuid)
rv_uuid = uuid.UUID(rv_x)
assert rv_uuid == test_uuid
def test_json_attr(self): def test_json_attr(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
@app.route('/add', methods=['POST']) @app.route('/add', methods=['POST'])
@ -337,7 +362,7 @@ class TestSendfile(object):
assert rv.data == f.read() assert rv.data == f.read()
rv.close() rv.close()
def test_send_file_xsendfile(self): def test_send_file_xsendfile(self, catch_deprecation_warnings):
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.use_x_sendfile = True app.use_x_sendfile = True
with app.test_request_context(): with app.test_request_context():
@ -349,90 +374,163 @@ class TestSendfile(object):
assert rv.mimetype == 'text/html' assert rv.mimetype == 'text/html'
rv.close() rv.close()
def test_send_file_object(self, catch_deprecation_warnings): def test_send_file_last_modified(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
with catch_deprecation_warnings() as captured: last_modified = datetime.datetime(1999, 1, 1)
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb') @app.route('/')
rv = flask.send_file(f) def index():
return flask.send_file(StringIO("party like it's"),
last_modified=last_modified,
mimetype='text/plain')
c = app.test_client()
rv = c.get('/')
assert rv.last_modified == last_modified
def test_send_file_object_without_mimetype(self):
app = flask.Flask(__name__)
with app.test_request_context():
with pytest.raises(ValueError) as excinfo:
flask.send_file(StringIO("LOL"))
assert 'Unable to infer MIME-type' in str(excinfo)
assert 'no filename is available' in str(excinfo)
with app.test_request_context():
flask.send_file(StringIO("LOL"), attachment_filename='filename')
def test_send_file_object(self):
app = flask.Flask(__name__)
with app.test_request_context():
with open(os.path.join(app.root_path, 'static/index.html'), mode='rb') as f:
rv = flask.send_file(f, mimetype='text/html')
rv.direct_passthrough = False rv.direct_passthrough = False
with app.open_resource('static/index.html') as f: with app.open_resource('static/index.html') as f:
assert rv.data == f.read() assert rv.data == f.read()
assert rv.mimetype == 'text/html' assert rv.mimetype == 'text/html'
rv.close() rv.close()
# mimetypes + etag
assert len(captured) == 2
app.use_x_sendfile = True app.use_x_sendfile = True
with catch_deprecation_warnings() as captured:
with app.test_request_context(): with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html')) with open(os.path.join(app.root_path, 'static/index.html')) as f:
rv = flask.send_file(f) rv = flask.send_file(f, mimetype='text/html')
assert rv.mimetype == 'text/html' assert rv.mimetype == 'text/html'
assert 'x-sendfile' in rv.headers assert 'x-sendfile' not in rv.headers
assert rv.headers['x-sendfile'] == \
os.path.join(app.root_path, 'static/index.html')
rv.close() rv.close()
# mimetypes + etag
assert len(captured) == 2
app.use_x_sendfile = False app.use_x_sendfile = False
with app.test_request_context(): with app.test_request_context():
with catch_deprecation_warnings() as captured: f = StringIO('Test')
f = StringIO('Test') rv = flask.send_file(f, mimetype='application/octet-stream')
rv = flask.send_file(f) rv.direct_passthrough = False
rv.direct_passthrough = False assert rv.data == b'Test'
assert rv.data == b'Test' assert rv.mimetype == 'application/octet-stream'
assert rv.mimetype == 'application/octet-stream' rv.close()
rv.close()
# etags class PyStringIO(object):
assert len(captured) == 1 def __init__(self, *args, **kwargs):
with catch_deprecation_warnings() as captured: self._io = StringIO(*args, **kwargs)
class PyStringIO(object): def __getattr__(self, name):
def __init__(self, *args, **kwargs): return getattr(self._io, name)
self._io = StringIO(*args, **kwargs) f = PyStringIO('Test')
def __getattr__(self, name): f.name = 'test.txt'
return getattr(self._io, name) rv = flask.send_file(f, attachment_filename=f.name)
f = PyStringIO('Test') rv.direct_passthrough = False
f.name = 'test.txt' assert rv.data == b'Test'
rv = flask.send_file(f) assert rv.mimetype == 'text/plain'
rv.direct_passthrough = False rv.close()
assert rv.data == b'Test'
assert rv.mimetype == 'text/plain' f = StringIO('Test')
rv.close() rv = flask.send_file(f, mimetype='text/plain')
# attachment_filename and etags rv.direct_passthrough = False
assert len(captured) == 3 assert rv.data == b'Test'
with catch_deprecation_warnings() as captured: assert rv.mimetype == 'text/plain'
f = StringIO('Test') rv.close()
rv = flask.send_file(f, mimetype='text/plain')
rv.direct_passthrough = False
assert rv.data == b'Test'
assert rv.mimetype == 'text/plain'
rv.close()
# etags
assert len(captured) == 1
app.use_x_sendfile = True app.use_x_sendfile = True
with catch_deprecation_warnings() as captured:
with app.test_request_context():
f = StringIO('Test')
rv = flask.send_file(f)
assert 'x-sendfile' not in rv.headers
rv.close()
# etags
assert len(captured) == 1
def test_attachment(self, catch_deprecation_warnings): with app.test_request_context():
f = StringIO('Test')
rv = flask.send_file(f, mimetype='text/html')
assert 'x-sendfile' not in rv.headers
rv.close()
@pytest.mark.skipif(
not callable(getattr(Range, 'to_content_range_header', None)),
reason="not implement within werkzeug"
)
def test_send_file_range_request(self):
app = flask.Flask(__name__)
@app.route('/')
def index():
return flask.send_file('static/index.html', conditional=True)
c = app.test_client()
rv = c.get('/', headers={'Range': 'bytes=4-15'})
assert rv.status_code == 206
with app.open_resource('static/index.html') as f:
assert rv.data == f.read()[4:16]
rv.close()
rv = c.get('/', headers={'Range': 'bytes=4-'})
assert rv.status_code == 206
with app.open_resource('static/index.html') as f:
assert rv.data == f.read()[4:]
rv.close()
rv = c.get('/', headers={'Range': 'bytes=4-1000'})
assert rv.status_code == 206
with app.open_resource('static/index.html') as f:
assert rv.data == f.read()[4:]
rv.close()
rv = c.get('/', headers={'Range': 'bytes=-10'})
assert rv.status_code == 206
with app.open_resource('static/index.html') as f:
assert rv.data == f.read()[-10:]
rv.close()
rv = c.get('/', headers={'Range': 'bytes=1000-'})
assert rv.status_code == 416
rv.close()
rv = c.get('/', headers={'Range': 'bytes=-'})
assert rv.status_code == 416
rv.close()
rv = c.get('/', headers={'Range': 'somethingsomething'})
assert rv.status_code == 416
rv.close()
last_modified = datetime.datetime.fromtimestamp(os.path.getmtime(
os.path.join(app.root_path, 'static/index.html'))).replace(
microsecond=0)
rv = c.get('/', headers={'Range': 'bytes=4-15',
'If-Range': http_date(last_modified)})
assert rv.status_code == 206
rv.close()
rv = c.get('/', headers={'Range': 'bytes=4-15', 'If-Range': http_date(
datetime.datetime(1999, 1, 1))})
assert rv.status_code == 200
rv.close()
def test_attachment(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
with catch_deprecation_warnings() as captured: with app.test_request_context():
with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html')) as f:
f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f, as_attachment=True,
rv = flask.send_file(f, as_attachment=True) attachment_filename='index.html')
value, options = parse_options_header(rv.headers['Content-Disposition']) value, options = \
parse_options_header(rv.headers['Content-Disposition'])
assert value == 'attachment' assert value == 'attachment'
rv.close() rv.close()
# mimetypes + etag
assert len(captured) == 2
with app.test_request_context(): with app.test_request_context():
assert options['filename'] == 'index.html' assert options['filename'] == 'index.html'
@ -737,3 +835,45 @@ class TestStreaming(object):
rv = c.get('/?name=World') rv = c.get('/?name=World')
assert rv.data == b'Hello World!' assert rv.data == b'Hello World!'
assert called == [42] assert called == [42]
class TestSafeJoin(object):
def test_safe_join(self):
# Valid combinations of *args and expected joined paths.
passing = (
(('a/b/c', ), 'a/b/c'),
(('/', 'a/', 'b/', 'c/', ), '/a/b/c'),
(('a', 'b', 'c', ), 'a/b/c'),
(('/a', 'b/c', ), '/a/b/c'),
(('a/b', 'X/../c'), 'a/b/c', ),
(('/a/b', 'c/X/..'), '/a/b/c', ),
# If last path is '' add a slash
(('/a/b/c', '', ), '/a/b/c/', ),
# Preserve dot slash
(('/a/b/c', './', ), '/a/b/c/.', ),
(('a/b/c', 'X/..'), 'a/b/c/.', ),
# Base directory is always considered safe
(('../', 'a/b/c'), '../a/b/c'),
(('/..', ), '/..'),
)
for args, expected in passing:
assert flask.safe_join(*args) == expected
def test_safe_join_exceptions(self):
# Should raise werkzeug.exceptions.NotFound on unsafe joins.
failing = (
# path.isabs and ``..'' checks
('/a', 'b', '/c'),
('/a', '../b/c', ),
('/a', '..', 'b/c'),
# Boundaries violations after path normalization
('/a', 'b/../b/../../c', ),
('/a', 'b', 'c/../..'),
('/a', 'b/../../c', ),
)
for args in failing:
with pytest.raises(NotFound):
print(flask.safe_join(*args))

35
tests/test_testing.py

@ -11,6 +11,7 @@
import pytest import pytest
import flask import flask
import werkzeug
from flask._compat import text_type from flask._compat import text_type
@ -43,6 +44,40 @@ def test_environ_defaults():
rv = c.get('/') rv = c.get('/')
assert rv.data == b'http://localhost/' assert rv.data == b'http://localhost/'
def test_environ_base_default():
app = flask.Flask(__name__)
app.testing = True
@app.route('/')
def index():
flask.g.user_agent = flask.request.headers["User-Agent"]
return flask.request.remote_addr
with app.test_client() as c:
rv = c.get('/')
assert rv.data == b'127.0.0.1'
assert flask.g.user_agent == 'werkzeug/' + werkzeug.__version__
def test_environ_base_modified():
app = flask.Flask(__name__)
app.testing = True
@app.route('/')
def index():
flask.g.user_agent = flask.request.headers["User-Agent"]
return flask.request.remote_addr
with app.test_client() as c:
c.environ_base['REMOTE_ADDR'] = '0.0.0.0'
c.environ_base['HTTP_USER_AGENT'] = 'Foo'
rv = c.get('/')
assert rv.data == b'0.0.0.0'
assert flask.g.user_agent == 'Foo'
c.environ_base['REMOTE_ADDR'] = '0.0.0.1'
c.environ_base['HTTP_USER_AGENT'] = 'Bar'
rv = c.get('/')
assert rv.data == b'0.0.0.1'
assert flask.g.user_agent == 'Bar'
def test_redirect_keep_session(): def test_redirect_keep_session():
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.secret_key = 'testing' app.secret_key = 'testing'

15
tox.ini

@ -1,14 +1,20 @@
[tox] [tox]
envlist = {py26,py27,pypy}-{lowest,release,devel}, {py33,py34,py35}-{release,devel} envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35}-{release,devel}{,-simplejson}
[testenv] [testenv]
passenv = LANG
usedevelop=true
commands = commands =
py.test [] # We need to install those after Flask is installed.
pip install -e examples/flaskr
pip install -e examples/minitwit
py.test --cov=flask --cov-report html []
deps= deps=
pytest pytest
pytest-cov
greenlet greenlet
redbaron
lowest: Werkzeug==0.7 lowest: Werkzeug==0.7
lowest: Jinja2==2.4 lowest: Jinja2==2.4
@ -19,6 +25,7 @@ deps=
devel: git+https://github.com/pallets/jinja.git devel: git+https://github.com/pallets/jinja.git
devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/pallets/itsdangerous.git
devel: git+https://github.com/jek/blinker.git devel: git+https://github.com/jek/blinker.git
simplejson: simplejson
[testenv:docs] [testenv:docs]
deps = sphinx deps = sphinx

Loading…
Cancel
Save