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
.cache/
.idea/
# Coverage reports
htmlcov
.coverage
.coverage.*
*,cover

9
.travis.yml

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

1
AUTHORS

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

23
CHANGES

@ -3,6 +3,25 @@ Flask Changelog
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
--------------
@ -41,7 +60,7 @@ Released on May 29th 2016, codename Absinthe.
from a view function.
- Added :meth:`flask.Config.from_json`.
- 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
can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config key.
- 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
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
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
see :ref:`instance-folders`.
- Added the ``APPLICATION_ROOT`` configuration variable.

46
CONTRIBUTING.rst

@ -36,7 +36,7 @@ Running the testsuite
---------------------
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
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
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
test:
FLASK_DEBUG= py.test tests examples
tox-test:
tox
pip install -r test-requirements.txt
tox -e py-release
audit:
python setup.py audit

2
README

@ -37,6 +37,8 @@
$ py.test
Details on contributing can be found in CONTRIBUTING.rst
~ Where can I get help?
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
build with caution, watching for exploits when building to your requirements.
The Status of Python 3
----------------------
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/>`_.
Python 3 Support in Flask
-------------------------
If you do want to dive into Python 3 already have a look at the
:ref:`python3-support` page.
Flask, its dependencies, and most Flask extensions all support Python 3.
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`.

8
docs/api.rst

@ -316,13 +316,7 @@ Useful Functions and Classes
.. autofunction:: url_for
.. function:: abort(code)
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:: abort
.. 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
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
-----------------------

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
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
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
internal utilities so that you can find the hook points needed for your
project.
@ -35,7 +35,7 @@ Subclass.
The :class:`~flask.Flask` class has many methods designed for subclassing. You
can quickly add or customize behavior by subclassing :class:`~flask.Flask` (see
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.
---------------------

21
docs/blueprints.rst

@ -176,16 +176,25 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
admin = Blueprint('admin', __name__, template_folder='templates')
As for static files, the path can be absolute or relative to the blueprint
resource folder. The template folder is added to the searchpath of
templates but with a lower priority than the actual application's template
folder. That way you can easily override templates that a blueprint
provides in the actual application.
For static files, the path can be absolute or relative to the blueprint
resource folder.
The template folder is added to the search path of templates but with a lower
priority than the actual application's template folder. That way you can
easily override templates that a blueprint provides in the actual application.
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
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
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
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
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.
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
mode of the application automatically by exporting ``FLASK_DEBUG``. If
set to ``1`` debug is enabled or ``0`` disables it.
Or with a filename::
set to ``1`` debug is enabled or ``0`` disables it::
export FLASK_DEBUG=1
@ -218,13 +216,13 @@ step.
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
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
to Flask which is through ``setuptools``. If you make a Python package that
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:
Example `setup.py`::
Example :file:`setup.py`::
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

78
docs/conf.py

@ -11,13 +11,16 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
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,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('_themes'))
sys.path.append(os.path.abspath('.'))
sys.path.append(os.path.join(os.path.dirname(__file__), '_themes'))
sys.path.append(os.path.dirname(__file__))
# -- General configuration -----------------------------------------------------
@ -46,22 +49,21 @@ master_doc = 'index'
# General information about the project.
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
# |version| and |release|, also used in various other places throughout the
# built documents.
import pkg_resources
try:
release = pkg_resources.get_distribution('Flask').version
except pkg_resources.DistributionNotFound:
print('Flask must be installed to build the documentation.')
print('Install from source using `pip install -e .` in a virtualenv.')
sys.exit(1)
del pkg_resources
if 'dev' in release:
release = release.split('dev')[0] + 'dev'
release = ''.join(release.partition('dev')[:2])
version = '.'.join(release.split('.')[:2])
# 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
# 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
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
'touch_icon': 'touch-icon.png'
}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
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
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# 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,
# 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.
html_sidebars = {
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
'**': ['sidebarlogo.html', 'localtoc.html', 'relations.html',
'sourcelink.html', 'searchbox.html']
'index': [
'sidebarintro.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
@ -187,8 +196,7 @@ htmlhelp_basename = 'Flaskdoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('latexindex', 'Flask.tex', u'Flask Documentation',
u'Armin Ronacher', 'manual'),
('latexindex', 'Flask.tex', u'Flask Documentation', u'Armin Ronacher', 'manual'),
]
# Documents to append as an appendix to all manuals.
@ -198,10 +206,10 @@ latex_documents = [
latex_use_modindex = False
latex_elements = {
'fontpkg': r'\usepackage{mathpazo}',
'papersize': 'a4paper',
'pointsize': '12pt',
'preamble': r'\usepackage{flaskstyle}'
'fontpkg': r'\usepackage{mathpazo}',
'papersize': 'a4paper',
'pointsize': '12pt',
'preamble': r'\usepackage{flaskstyle}'
}
latex_use_parts = True
@ -245,21 +253,23 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf']
#epub_tocdepth = 3
intersphinx_mapping = {
'https://docs.python.org/dev': None,
'http://werkzeug.pocoo.org/docs/': None,
'http://click.pocoo.org/': None,
'http://jinja.pocoo.org/docs/': None,
'http://www.sqlalchemy.org/docs/': None,
'https://wtforms.readthedocs.org/en/latest/': None,
'https://pythonhosted.org/blinker/': None
'python': ('https://docs.python.org/3/', None),
'werkzeug': ('http://werkzeug.pocoo.org/docs/', None),
'click': ('http://click.pocoo.org/', None),
'jinja': ('http://jinja.pocoo.org/docs/', None),
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None),
'blinker': ('https://pythonhosted.org/blinker/', None)
}
pygments_style = 'flask_theme_support.FlaskyStyle'
# fall back if theme is not there
try:
__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('Warning: Flask themes unavailable. Building with default theme')
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('-' * 74)
pygments_style = 'tango'
html_theme = 'default'
html_theme_options = {}
# 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.
This is not recommended but might give
you a performance improvement on the
cost of cachability.
cost of cacheability.
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default)
jsonify responses will be pretty printed
if they are not requested by an
@ -262,7 +262,7 @@ So a common pattern is this::
This first loads the configuration from the
`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
Linux or OS X with the export command in the shell before starting the
server::
@ -310,6 +310,7 @@ that experience:
limit yourself to request-only accesses to the configuration you can
reconfigure the object later on as needed.
.. _config-dev-prod:
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 Webfaction <http://flask.pocoo.org/snippets/65/>`_
- `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/>`_
- `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
-------------------

8
docs/deploying/mod_wsgi.rst

@ -130,12 +130,12 @@ to httpd 2.4 syntax
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/
.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide
.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi
.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html
.. _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
---------------

2
docs/deploying/uwsgi.rst

@ -29,7 +29,7 @@ Given a flask application in myapp.py, use the following command:
.. 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,
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/
.. _eventlet: http://eventlet.net/
.. _greenlet: http://greenlet.readthedocs.org/en/latest/
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
Gevent
-------
@ -41,7 +41,7 @@ event loop::
http_server.serve_forever()
.. _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
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
<http://www.getsentry.com/>`_ for dealing with application errors. It's
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
aggregates duplicate errors, captures the full stack trace and local
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
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
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.
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.
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
do that in the log formatter format string.
Here some example setups:
Here are some example setups:
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 |
| | issued (if available). |
+------------------+----------------------------------------------------+
| ``%(asctime)s`` | Human-readable time when the LogRecord` was |
| | created. By default this is of the form |
| ``%(asctime)s`` | Human-readable time when the |
| | :class:`~logging.LogRecord` was created. |
| | By default this is of the form |
| | ``"2003-07-08 16:49:45,896"`` (the numbers after |
| | the comma are millisecond portion of the time). |
| | This can be changed by subclassing the formatter |

7
docs/extensions.rst

@ -25,6 +25,13 @@ importable from ``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
----------------

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
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
commands will work for you::
$ sudo easy_install virtualenv
or even better::
If you are on Mac OS X or Linux, chances are that the following
command will work for you::
$ 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::
$ 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
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
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::
$ venv\scripts\activate
$ venv\Scripts\activate
Either way, you should now be using your virtualenv (notice how the prompt of
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
:command:`pip` with root privileges::
``pip`` with root privileges::
$ sudo pip install Flask
(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
------------------
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.
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
origin`` to update to the latest version.
.. _windows-easy-install:
`pip` and `setuptools` on Windows
---------------------------------
Sometimes getting the standard "Python packaging tools" like *pip*, *setuptools*
and *virtualenv* can be a little trickier, but nothing very hard. The two crucial
packages you will need are setuptools and pip - these will let you install
anything else (like virtualenv). Fortunately there are two "bootstrap scripts"
you can run to install either.
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools``
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial
package you will need is pip - this will let you install
anything else (like virtualenv). Fortunately there is a "bootstrap script"
you can run to install.
If you don't currently have either, then `get-pip.py` will install both for you
(you won't need to run ez_setup.py).
If you don't currently have ``pip``, then `get-pip.py` will install it for you.
`get-pip.py`_
To install the latest setuptools, you can use its bootstrap file:
`ez_setup.py`_
Either should be double-clickable once you download them. If you already have pip,
It should be double-clickable once you download it. If you already have ``pip``,
you can upgrade them by running::
> pip install --upgrade pip setuptools
Most often, once you pull up a command prompt you want to be able to type :command:`pip`
and :command:`python` which will run those things, but this might not automatically happen
Most often, once you pull up a command prompt you want to be able to type ``pip``
and ``python`` which will run those things, but this might not automatically happen
on Windows, because it doesn't know where those executables are (give either a try!).
To fix this, you should be able to navigate to your Python install directory
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
check that you can now just type :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::
@ -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.
.. _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
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'])
celery.conf.update(app.config)
TaskBase = celery.Task

45
docs/patterns/distribute.rst

@ -39,10 +39,8 @@ the process, also read the :ref:`fabric-deployment` chapter.
Basic Setup Script
------------------
Because you have Flask running, you have setuptools available on your system anyways.
Flask already depends upon setuptools. If you do not, fear not, there is a
script to install it for you: `ez_setup.py`_. Just download and
run with your Python interpreter.
Because you have Flask installed, you have setuptools available on your system.
Flask already depends upon setuptools.
Standard disclaimer applies: :ref:`you better use a 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
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
@ -76,17 +74,36 @@ the `find_packages` function::
packages=find_packages()
)
Most parameters to the `setup` function should be self explanatory,
`include_package_data` and `zip_safe` might not be.
`include_package_data` tells setuptools to look for a :file:`MANIFEST.in` file
Most parameters to the ``setup`` function should be self explanatory,
``include_package_data`` and ``zip_safe`` might not be.
``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
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
your packages to be installed as zip files because some tools do not
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
@ -104,13 +121,13 @@ your tarball::
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`
parameter of the `setup` function to ``True``!
parameter of the ``setup`` function to ``True``!
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
PyPI on installation. By default it will always use the most recent
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
: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
and install all dependencies::
$ python setup.py install
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
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
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

38
docs/patterns/fabric.rst

@ -43,36 +43,25 @@ virtual environment::
env.hosts = ['server1.example.com', 'server2.example.com']
def pack():
# create a new source distribution as tarball
# build the package
local('python setup.py sdist --formats=gztar', capture=False)
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()
# upload the source tarball to the temporary folder on the server
put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
# create a place where we can unzip the tarball, then enter
# that directory and unzip it
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')
filename = '%s.tar.gz' % dist
# upload the package to the temporary folder on the server
put('dist/%s' % filename, '/tmp/%s' % filename)
The example above is well documented and should be straightforward. Here
a recap of the most common commands fabric provides:
# install the package in the application's virtualenv with pip
run('/var/www/yourapplication/env/bin/pip install /tmp/%s' % filename)
- `run` - executes a command on a remote server
- `local` - executes a command on the local machine
- `put` - uploads a file to the remote server
- `cd` - changes the directory on the serverside. This has to be used
in combination with the ``with`` statement.
# remove the uploaded package
run('rm -r /tmp/%s' % filename)
# touch the .wsgi file to trigger a reload in mod_wsgi
run('touch /var/www/yourapplication.wsgi')
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
upload them ahead of time by hand.
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):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET', 'POST'])
def upload_file():
@ -71,7 +71,7 @@ the file and redirects the user to the URL for the uploaded file::
<!doctype html>
<title>Upload new File</title>
<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>
<input type=submit value=Upload>
</form>
@ -104,9 +104,9 @@ before storing it directly on the filesystem.
>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'
Now one last thing is missing: the serving of the uploaded files. In the
:func:`upload_file()` we redirect the user to
``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``.
Now one last thing is missing: the serving of the uploaded files. In the
:func:`upload_file()` we redirect the user to
``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
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
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
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
---------------
@ -76,7 +78,7 @@ And here is the :file:`login.html` template which also inherits from
{% if error %}
<p class=error><strong>Error:</strong> {{ error }}
{% endif %}
<form action="" method=post>
<form method=post>
<dl>
<dt>Username:
<dd><input type=text name=username value="{{

1
docs/patterns/index.rst

@ -41,3 +41,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
methodoverrides
requestchecksum
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
write this by having a function that calls into
: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)
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')
url('/user/<username>', 'views.user')
# add a single route to the index view
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
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():
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
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
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
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):
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
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
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
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
returns a function. Pretty simple actually. The only thing you have to
keep in mind when implementing something like this is to update the
`__name__`, `__module__` and some other attributes of a function. This is
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`).
wraps and replaces another function. Since the original function is
replaced, you need to remember to copy the original function's information
to the new function. Use :func:`functools.wraps` to handle this for you.
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
logged in::
the current user is stored in ``g.user`` and is ``None`` if there is no-one
logged in. ::
from functools import wraps
from flask import g, request, redirect, url_for
@ -37,15 +35,24 @@ logged in::
return f(*args, **kwargs)
return decorated_function
So how would you use that decorator now? Apply it as innermost decorator
to a view function. When applying further decorators, always remember
that the :meth:`~flask.Flask.route` decorator is the outermost::
To use the decorator, apply it as innermost decorator to a view function.
When applying further decorators, always remember
that the :meth:`~flask.Flask.route` decorator is the outermost. ::
@app.route('/secret_page')
@login_required
def secret_page():
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
-----------------

6
docs/patterns/wtforms.rst

@ -108,7 +108,7 @@ takes advantage of the :file:`_formhelpers.html` template:
.. sourcecode:: html+jinja
{% from "_formhelpers.html" import render_field %}
<form method=post action="/register">
<form method=post>
<dl>
{{ render_field(form.username) }}
{{ 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
website`_.
.. _WTForms: http://wtforms.readthedocs.org/
.. _WTForms website: http://wtforms.readthedocs.org/
.. _WTForms: https://wtforms.readthedocs.io/
.. _WTForms website: https://wtforms.readthedocs.io/

39
docs/python3.rst

@ -3,32 +3,21 @@
Python 3 Support
================
Flask and all of its dependencies support Python 3 so you can in theory
start working on it already. There are however a few things you should be
aware of before you start using Python 3 for your next project.
Flask, its dependencies, and most Flask extensions support Python 3.
You should 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
higher. 3.2 and older are *not* supported.
You need to use Python 3.3 or higher. 3.2 and older are *not* supported.
In addition to that you need to use the latest and greatest versions of
`itsdangerous`, `Jinja2` and `Werkzeug`. Flask 0.10 and Werkzeug 0.9 were
the first versions to introduce Python 3 support.
You should use the latest versions of all Flask-related packages.
Flask 0.10 and Werkzeug 0.9 were the first versions to introduce Python 3 support.
Some of the decisions made in regards to unicode and byte utilization on
Python 3 make it hard to write low level code. This mainly affects WSGI
middlewares and interacting with the WSGI provided information. Werkzeug
wraps all that information in high-level helpers but some of those were
specifically added for the Python 3 support and are quite new.
Python 3 changed how unicode and bytes are handled, which complicates how low
level code handles HTTP data. This mainly affects WSGI middleware interacting
with the WSGI ``environ`` data. Werkzeug wraps that information in high-level
helpers, so encoding issues should not affect you.
Unless you require absolute compatibility, you should be fine with Python 3
nowadays. Most libraries and Flask extensions have been ported by now and
using Flask with Python 3 is generally a smooth ride. However, keep in mind
that most libraries (including Werkzeug and Flask) might not be quite as
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.
The majority of the upgrade work is in the lower-level libraries like
Flask and Werkzeug, not the high-level application code.
For example, all of the examples in the Flask repository work on both Python 2 and 3
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.
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
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
(``'__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
@ -42,17 +42,17 @@ your application :file:`flask.py` because this would conflict with Flask
itself.
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
`FLASK_APP` environment variable::
``FLASK_APP`` environment variable::
$ export FLASK_APP=hello.py
$ flask run
* Running on http://127.0.0.1:5000/
If you are on Windows you need to use `set` instead of `export`.
If you are on Windows 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
$ python -m flask run
@ -86,7 +86,7 @@ should see your hello world greeting.
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
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
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`
docs to see the alternative method for running a server.
Invalid Import Name
```````````````````
The :option:`-a` argument to :command:`flask` is the name of the module to
import. In case that module is incorrectly named you will get an import
error upon start (or if debug is enabled when you navigate to the
application). It will tell you what it tried to import and why it failed.
The ``FLASK_APP`` environment variable is the name of the module to import at
:command:`flask run`. In case that module is incorrectly named you will get an
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.
The most common reason is a typo or because you did not actually create an
``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
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::
$ export FLASK_DEBUG=1
$ 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:
@ -202,7 +202,7 @@ The following converters exist:
=========== ===============================================
`string` accepts any text without a slash (the default)
`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
`any` matches one of the items provided
`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
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
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
of keyword arguments, each corresponding to the variable part of the URL rule.
Unknown variable parts are appended to the URL as query parameters. Here are
some examples:
>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
... print url_for('index')
... print url_for('login')
... print url_for('login', next='/')
... print url_for('profile', username='John Doe')
...
/
/login
/login?next=/
/user/John%20Doe
some examples::
>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
... print url_for('index')
... print url_for('login')
... print url_for('login', next='/')
... print url_for('profile', username='John Doe')
...
/
/login
/login?next=/
/user/John%20Doe
(This also uses the :meth:`~flask.Flask.test_request_context` method, explained
below. It tells Flask to behave as though it is handling a request, even
@ -288,8 +288,8 @@ There are three good reasons for this:
remember to change URLs all over the place.
2. URL building will handle escaping of special characters and Unicode
data transparently for you, so you don't have to deal with them.
3. If your application is placed outside the URL root (say, in
``/myapplication`` instead of ``/``), :func:`~flask.url_for` will handle
3. If your application is placed outside the URL root - say, in
``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle
that properly for you.
@ -298,7 +298,7 @@ HTTP Methods
HTTP (the protocol web applications are speaking) knows different methods for
accessing URLs. By default, a route only answers to ``GET`` requests, but that
can be changed by providing the `methods` argument to the
can be changed by providing the ``methods`` argument to the
:meth:`~flask.Flask.route` decorator. Here are some examples::
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
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
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
:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the
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
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up \xbb HTML'
>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up \xbb HTML'
.. versionchanged:: 0.5
@ -540,7 +540,7 @@ The Request Object
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
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
@ -563,7 +563,7 @@ attributes mentioned above::
# was GET or the credentials were invalid
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
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
@ -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
information. Such tuples have to be in the form ``(response, status,
headers)`` or ``(response, headers)`` where at least one item has
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.
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.
4. If none of that works, Flask will assume the return value is a
valid WSGI application and convert that into a response object.
If you want to get hold of the resulting response object inside the view
you can use the :func:`~flask.make_response` function.
Imagine you have a view like this:
.. sourcecode:: python
Imagine you have a view like this::
@app.errorhandler(404)
def not_found(error):
@ -743,9 +741,7 @@ Imagine you have a view like this:
You just need to wrap the return expression with
:func:`~flask.make_response` and get the response object to modify it, then
return it:
.. sourcecode:: python
return it::
@app.errorhandler(404)
def not_found(error):
@ -784,7 +780,7 @@ sessions work::
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form action="" method="post">
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</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
a secret key should be as random as possible. Your operating system
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
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
>>> import os
>>> os.urandom(24)
'\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
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
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
----------------

14
docs/testing.rst

@ -152,13 +152,13 @@ invalid credentials. Add this new test to the class::
def test_login_logout(self):
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()
assert 'You were logged out' in rv.data
assert b'You were logged out' in rv.data
rv = self.login('adminx', 'default')
assert 'Invalid username' in rv.data
assert b'Invalid username' in rv.data
rv = self.login('admin', 'defaultx')
assert 'Invalid password' in rv.data
assert b'Invalid password' in rv.data
Test Adding Messages
--------------------
@ -172,9 +172,9 @@ like this::
title='<Hello>',
text='<strong>HTML</strong> allowed here'
), follow_redirects=True)
assert 'No entries here so far' not in rv.data
assert '&lt;Hello&gt;' in rv.data
assert '<strong>HTML</strong> allowed here' in rv.data
assert b'No entries here so far' not in rv.data
assert b'&lt;Hello&gt;' 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,
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():
"""Initializes the database."""
init_db()
print 'Initialized the database.'
print('Initialized the database.')
The ``app.cli.command()`` decorator registers a new command with the
: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
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.
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
from flaskr import app
from .flaskr import app
This import statement brings the application instance into the top-level
of the application package. When it is time to run the application, the

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
updating experience.
If you want to use the :command:`easy_install` command to upgrade your Flask
installation, make sure to pass it the :option:`-U` parameter::
Use the :command:`pip` command to upgrade your existing Flask installation by
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
------------
@ -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
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
application.
@ -68,7 +106,7 @@ Templating
The :func:`~flask.templating.render_template_string` function has changed to
autoescape template variables by default. This better matches the behavior
of :func:`~flask.templating.render_template`.
Extension imports
`````````````````
@ -133,7 +171,7 @@ Version 0.8
-----------
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
object. With that introduction we moved the implementation details for
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
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
windows including cygwin, msysgit or ming32. Also source control systems
like svn, hg or git have builtin support for applying unified diffs as
@ -316,7 +354,7 @@ to upgrade. What changed?
runtime.
- Blueprints have an inverse behavior for :meth:`url_for`. Previously
``.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
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
@ -334,7 +372,7 @@ to upgrade. What changed?
name into that folder if you want :file:`blueprintname/template.html` as
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
recommend upgrading to the new blueprints as they provide a lot of useful
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
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
it with the current request object. This makes it easier to extend
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
code easier to understand and fixes a couple of small issues that make
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
: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 %}
<ul class="entries">
{% for entry in entries %}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}</li>
{% else %}
<li><em>Unbelievable. No entries here so far</em>
<li><em>Unbelievable. No entries here so far</em></li>
{% endfor %}
</ul>
{% endblock %}

2
examples/flaskr/setup.cfg

@ -1,2 +1,2 @@
[aliases]
[tool: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.
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.
jsonify = json.jsonify

78
flask/app.py

@ -14,7 +14,6 @@ from threading import Lock
from datetime import timedelta
from itertools import chain
from functools import update_wrapper
from collections import deque
from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule, RequestRedirect, BuildError
@ -519,7 +518,7 @@ class Flask(_PackageBoundObject):
#: def to_python(self, value):
#: return value.split(',')
#: def to_url(self, values):
#: return ','.join(BaseConverter.to_url(value)
#: return ','.join(super(ListConverter, self).to_url(value)
#: for value in values)
#:
#: app = Flask(__name__)
@ -838,12 +837,11 @@ class Flask(_PackageBoundObject):
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
options.setdefault('passthrough_errors', True)
try:
run_simple(host, port, self, **options)
finally:
# 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.
self._got_first_request = False
@ -877,9 +875,9 @@ class Flask(_PackageBoundObject):
from flask.testing import FlaskClient
class CustomClient(FlaskClient):
def __init__(self, authentication=None, *args, **kwargs):
FlaskClient.__init__(*args, **kwargs)
self._authentication = authentication
def __init__(self, *args, **kwargs):
self._authentication = kwargs.pop("authentication")
super(CustomClient,self).__init__( *args, **kwargs)
app.test_client_class = CustomClient
client = app.test_client(authentication='Basic ....')
@ -935,22 +933,7 @@ class Flask(_PackageBoundObject):
@setupmethod
def register_blueprint(self, blueprint, **options):
"""Register a blueprint on the application. For information about
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
"""Registers a blueprint on the application.
.. versionadded:: 0.7
"""
@ -1131,7 +1114,7 @@ class Flask(_PackageBoundObject):
@setupmethod
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::
@app.errorhandler(404)
@ -1169,7 +1152,8 @@ class Flask(_PackageBoundObject):
that do not necessarily have to be a subclass of the
: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):
self._register_error_handler(None, code_or_exception, f)
@ -1452,24 +1436,13 @@ class Flask(_PackageBoundObject):
def find_handler(handler_map):
if not handler_map:
return
queue = deque(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)
for cls in exc_class.__mro__:
handler = handler_map.get(cls)
if handler is not None:
# cache for next time exc_class is raised
handler_map[exc_class] = handler
return handler
queue.extend(cls.__mro__)
# try blueprint handlers
handler = find_handler(self.error_handler_spec
.get(request.blueprint, {})
@ -1571,7 +1544,7 @@ class Flask(_PackageBoundObject):
self.log_exception((exc_type, exc_value, tb))
if handler is None:
return InternalServerError()
return handler(e)
return self.finalize_request(handler(e), from_error_handler=True)
def log_exception(self, exc_info):
"""Logs an exception. This is called by :meth:`handle_exception`
@ -1639,9 +1612,30 @@ class Flask(_PackageBoundObject):
rv = self.dispatch_request()
except Exception as 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.process_response(response)
request_finished.send(self, response=response)
try:
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
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
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.
"""
bp = _request_ctx_stack.top.request.blueprint
@ -1988,7 +1982,7 @@ class Flask(_PackageBoundObject):
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
response = self.handle_exception(e)
return response(environ, start_response)
finally:
if self.should_ignore_error(error):

47
flask/cli.py

@ -18,7 +18,7 @@ import click
from ._compat import iteritems, reraise
from .helpers import get_debug_flag
from . import __version__
class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded."""
@ -86,7 +86,13 @@ def locate_app(app_id):
module = app_id
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]
if app_obj is None:
app = find_best_app(mod)
@ -108,10 +114,26 @@ def find_default_import_path():
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):
"""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
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.
"""
@ -270,12 +292,19 @@ class FlaskGroup(AppGroup):
:param add_default_commands: if this is True then the default run and
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
and returns the loaded app.
"""
def __init__(self, add_default_commands=True, create_app=None, **extra):
AppGroup.__init__(self, **extra)
def __init__(self, add_default_commands=True, create_app=None,
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
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)
# 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.
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
# 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="""\
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
Flask itself.
@ -449,7 +478,7 @@ The most useful commands are the "run" and "shell" command.
Example usage:
\b
%(prefix)s%(cmd)s FLASK_APP=hello
%(prefix)s%(cmd)s FLASK_APP=hello.py
%(prefix)s%(cmd)s FLASK_DEBUG=1
%(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
- 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 usage::
Example of module-based configuration::
app.config.from_object('yourapplication.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
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
"""
if isinstance(obj, string_types):

2
flask/exthook.py

@ -68,7 +68,7 @@ class ExtensionImporter(object):
warnings.warn(
"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:

146
flask/helpers.py

@ -25,8 +25,9 @@ try:
except ImportError:
from urlparse import quote as url_quote
from werkzeug.datastructures import Headers
from werkzeug.exceptions import BadRequest, NotFound
from werkzeug.datastructures import Headers, Range
from werkzeug.exceptions import BadRequest, NotFound, \
RequestedRangeNotSatisfiable
# this was moved in 0.7
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,
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
most efficient method available and configured. By default it will
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
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;
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
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`.
This is relative to the :attr:`~Flask.root_path`
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
file pointer is positioned at the start of data to
send before calling :func:`send_file`.
:param mimetype: the mimetype of the file if provided, otherwise
auto detection happens.
:param mimetype: the mimetype of the file if provided. If a file path is
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
a ``Content-Disposition: attachment`` header.
: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
:meth:`~Flask.get_send_file_max_age` of
: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
fsize = None
if isinstance(filename_or_fp, string_types):
filename = filename_or_fp
if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename)
file = None
if attachment_filename is None:
attachment_filename = os.path.basename(filename)
else:
from warnings import warn
file = filename_or_fp
filename = getattr(file, 'name', 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]
filename = 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()
if as_attachment:
if attachment_filename is None:
if filename is None:
raise TypeError('filename unavailable, required for '
'sending as attachment')
attachment_filename = os.path.basename(filename)
raise TypeError('filename unavailable, required for '
'sending as attachment')
headers.add('Content-Disposition', 'attachment',
filename=attachment_filename)
@ -529,22 +541,24 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
if file is not None:
file.close()
headers['X-Sendfile'] = filename
headers['Content-Length'] = os.path.getsize(filename)
fsize = os.path.getsize(filename)
headers['Content-Length'] = fsize
data = None
else:
if file is None:
file = open(filename, 'rb')
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)
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
direct_passthrough=True)
# if we know the file modification date, we can store it as
# the time of the last modification.
if mtime is not None:
rv.last_modified = int(mtime)
if last_modified is not None:
rv.last_modified = last_modified
elif mtime is not None:
rv.last_modified = mtime
rv.cache_control.public = True
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)
if add_etags and filename is not None:
from warnings import warn
try:
rv.set_etag('%s-%s-%s' % (
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 '
'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)
# make sure we don't send x-sendfile for servers that
# ignore the 304 status code for x-sendfile.
if rv.status_code == 304:
rv.headers.pop('x-sendfile', None)
# make sure we don't send x-sendfile for servers that
# ignore the 304 status code for x-sendfile.
if rv.status_code == 304:
rv.headers.pop('x-sendfile', None)
return rv
def safe_join(directory, filename):
"""Safely join `directory` and `filename`.
def safe_join(directory, *pathnames):
"""Safely join `directory` and zero or more untrusted `pathnames`
components.
Example usage::
@ -587,20 +614,23 @@ def safe_join(directory, filename):
with open(filename, 'rb') as fd:
content = fd.read() # Read and process the file content...
:param directory: the base directory.
:param filename: the untrusted filename relative to that directory.
:raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path
would fall out of `directory`.
:param directory: the trusted base directory.
:param pathnames: the untrusted pathnames relative to that directory.
:raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed
paths fall out of its boundaries.
"""
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
for filename in pathnames:
if filename != '':
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
raise NotFound()
if os.path.isabs(filename) or \
filename == '..' or \
filename.startswith('../'):
raise NotFound()
return os.path.join(directory, filename)
directory = os.path.join(directory, filename)
return directory
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
# depend anyways.
try:
from itsdangerous import simplejson as _json
except ImportError:
from itsdangerous import json as _json
from itsdangerous import json as _json
# 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.addHandler(debug_handler)
logger.addHandler(prod_handler)
# Disable propagation by default
logger.propagate = False
return logger

2
flask/sessions.py

@ -168,7 +168,7 @@ class SessionInterface(object):
null_session_class = NullSession
#: 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.
#:
#: .. versionadded:: 0.10

2
flask/signals.py

@ -37,7 +37,7 @@ except ImportError:
temporarily_connected_to = connected_to = _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.
_signals = Namespace()

14
flask/testing.py

@ -10,6 +10,7 @@
:license: BSD, see LICENSE for more details.
"""
import werkzeug
from contextlib import contextmanager
from werkzeug.test import Client, EnvironBuilder
from flask import _request_ctx_stack
@ -43,11 +44,23 @@ class FlaskClient(Client):
information about how to use this class refer to
: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.
"""
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
def session_transaction(self, *args, **kwargs):
"""When used in combination with a ``with`` statement this opens a
@ -101,6 +114,7 @@ class FlaskClient(Client):
def open(self, *args, **kwargs):
kwargs.setdefault('environ_overrides', {}) \
['flask._preserve_context'] = self.preserve_context
kwargs.setdefault('environ_base', self.environ_base)
as_tuple = kwargs.pop('as_tuple', False)
buffered = kwargs.pop('buffered', False)

2
flask/views.py

@ -123,7 +123,7 @@ class MethodViewType(type):
class MethodView(with_metaclass(MethodViewType, View)):
"""Like a regular class-based view but that dispatches requests to
particular methods. For instance if you implement a method called
:meth:`get` it means 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
forward your request to that. Also :attr:`options` is set for you
automatically::

3
scripts/flask-07-upgrade.py

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

5
scripts/make-release.py

@ -10,6 +10,7 @@
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from __future__ import print_function
import sys
import os
import re
@ -85,12 +86,12 @@ def build_and_upload():
def fail(message, *args):
print >> sys.stderr, 'Error:', message % args
print('Error:', message % args, file=sys.stderr)
sys.exit(1)
def info(message, *args):
print >> sys.stderr, message % args
print(message % args, file=sys.stderr)
def get_git_tags():

7
setup.cfg

@ -4,5 +4,8 @@ release = egg_info -RDb ''
[wheel]
universal = 1
[pytest]
norecursedirs = .* *.egg *.egg-info env* artwork docs examples
[metadata]
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
* Running on http://localhost:5000/
Ready for production? `Read this first <http://flask.pocoo.org/docs/deploying/>`.
Links
`````

1
test-requirements.txt

@ -0,0 +1 @@
tox

11
tests/conftest.py

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

57
tests/test_basic.py

@ -768,6 +768,29 @@ def test_error_handling():
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():
app = flask.Flask(__name__)
@ -1031,6 +1054,14 @@ def test_jsonify_mimetype():
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():
app = flask.Flask(__name__)
@ -1116,6 +1147,30 @@ def test_static_files():
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():
app = flask.Flask(__name__)
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)
app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions
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():

79
tests/test_cli.py

@ -12,6 +12,8 @@
# Copyright (C) 2015 CERN.
#
from __future__ import absolute_import, print_function
import os
import sys
import click
import pytest
@ -19,7 +21,8 @@ from click.testing import CliRunner
from flask import Flask, current_app
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):
@ -29,31 +32,47 @@ def test_cli_name(test_apps):
def test_find_best_app(test_apps):
"""Test of find_best_app."""
class mod:
"""Test if `find_best_app` behaves as expected with different combinations of input."""
class Module:
app = Flask('appname')
assert find_best_app(mod) == mod.app
assert find_best_app(Module) == Module.app
class mod:
class Module:
application = Flask('appname')
assert find_best_app(mod) == mod.application
assert find_best_app(Module) == Module.application
class mod:
class Module:
myapp = Flask('appname')
assert find_best_app(mod) == mod.myapp
assert find_best_app(Module) == Module.myapp
class mod:
myapp = Flask('appname')
myapp2 = Flask('appname2')
class Module:
pass
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):
assert prepare_exec_for_file('test.py') == 'test'
assert prepare_exec_for_file('/usr/share/__init__.py') == 'share'
"""Expect the correct path to be set and the correct module name to be returned.
: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):
prepare_exec_for_file('test.txt')
prepare_exec_for_file('/tmp/share/test.txt')
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:testapp").name == "testapp"
assert locate_app("cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app, "notanpp.py")
pytest.raises(NoAppException, locate_app, "cliapp/app")
pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp")
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):
"""Test of ScriptInfo."""
obj = ScriptInfo(app_import_path="cliapp.app:testapp")

20
tests/test_deprecations.py

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

12
tests/test_ext.py

@ -20,6 +20,18 @@ except ImportError:
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)
def importhook_setup(monkeypatch, request):
# we clear this out for various reasons. The most important one is

300
tests/test_helpers.py

@ -12,10 +12,13 @@
import pytest
import os
import uuid
import datetime
import flask
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 http_date
from flask._compat import StringIO, text_type
@ -99,20 +102,28 @@ class TestJSON(object):
rv = flask.json.dumps(u'\N{SNOWMAN}')
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."""
# 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__)
c = app.test_client()
for i, d in enumerate(test_data):
url = '/jsonify_basic_types{0}'.format(i)
app.add_url_rule(url, str(i), lambda x=d: flask.jsonify(x))
rv = c.get(url)
assert rv.mimetype == 'application/json'
assert flask.json.loads(rv.data) == d
url = '/jsonify_basic_types'
app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x))
rv = c.get(url)
assert rv.mimetype == 'application/json'
assert flask.json.loads(rv.data) == test_value
def test_jsonify_dicts(self):
"""Test jsonify with dicts and kwargs unpacking."""
@ -156,12 +167,10 @@ class TestJSON(object):
def test_jsonify_date_types(self):
"""Test jsonify with datetime.date and datetime.datetime types."""
test_dates = (
datetime.datetime(1973, 3, 11, 6, 30, 45),
datetime.date(1975, 1, 5)
)
app = flask.Flask(__name__)
c = app.test_client()
@ -172,6 +181,22 @@ class TestJSON(object):
assert rv.mimetype == 'application/json'
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):
app = flask.Flask(__name__)
@app.route('/add', methods=['POST'])
@ -337,7 +362,7 @@ class TestSendfile(object):
assert rv.data == f.read()
rv.close()
def test_send_file_xsendfile(self):
def test_send_file_xsendfile(self, catch_deprecation_warnings):
app = flask.Flask(__name__)
app.use_x_sendfile = True
with app.test_request_context():
@ -349,90 +374,163 @@ class TestSendfile(object):
assert rv.mimetype == 'text/html'
rv.close()
def test_send_file_object(self, catch_deprecation_warnings):
def test_send_file_last_modified(self):
app = flask.Flask(__name__)
with catch_deprecation_warnings() as captured:
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb')
rv = flask.send_file(f)
last_modified = datetime.datetime(1999, 1, 1)
@app.route('/')
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
with app.open_resource('static/index.html') as f:
assert rv.data == f.read()
assert rv.mimetype == 'text/html'
rv.close()
# mimetypes + etag
assert len(captured) == 2
app.use_x_sendfile = True
with catch_deprecation_warnings() as captured:
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'))
rv = flask.send_file(f)
with app.test_request_context():
with open(os.path.join(app.root_path, 'static/index.html')) as f:
rv = flask.send_file(f, mimetype='text/html')
assert rv.mimetype == 'text/html'
assert 'x-sendfile' in rv.headers
assert rv.headers['x-sendfile'] == \
os.path.join(app.root_path, 'static/index.html')
assert 'x-sendfile' not in rv.headers
rv.close()
# mimetypes + etag
assert len(captured) == 2
app.use_x_sendfile = False
with app.test_request_context():
with catch_deprecation_warnings() as captured:
f = StringIO('Test')
rv = flask.send_file(f)
rv.direct_passthrough = False
assert rv.data == b'Test'
assert rv.mimetype == 'application/octet-stream'
rv.close()
# etags
assert len(captured) == 1
with catch_deprecation_warnings() as captured:
class PyStringIO(object):
def __init__(self, *args, **kwargs):
self._io = StringIO(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._io, name)
f = PyStringIO('Test')
f.name = 'test.txt'
rv = flask.send_file(f)
rv.direct_passthrough = False
assert rv.data == b'Test'
assert rv.mimetype == 'text/plain'
rv.close()
# attachment_filename and etags
assert len(captured) == 3
with catch_deprecation_warnings() as captured:
f = StringIO('Test')
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
f = StringIO('Test')
rv = flask.send_file(f, mimetype='application/octet-stream')
rv.direct_passthrough = False
assert rv.data == b'Test'
assert rv.mimetype == 'application/octet-stream'
rv.close()
class PyStringIO(object):
def __init__(self, *args, **kwargs):
self._io = StringIO(*args, **kwargs)
def __getattr__(self, name):
return getattr(self._io, name)
f = PyStringIO('Test')
f.name = 'test.txt'
rv = flask.send_file(f, attachment_filename=f.name)
rv.direct_passthrough = False
assert rv.data == b'Test'
assert rv.mimetype == 'text/plain'
rv.close()
f = StringIO('Test')
rv = flask.send_file(f, mimetype='text/plain')
rv.direct_passthrough = False
assert rv.data == b'Test'
assert rv.mimetype == 'text/plain'
rv.close()
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__)
with catch_deprecation_warnings() as captured:
with app.test_request_context():
f = open(os.path.join(app.root_path, 'static/index.html'))
rv = flask.send_file(f, as_attachment=True)
value, options = parse_options_header(rv.headers['Content-Disposition'])
with app.test_request_context():
with open(os.path.join(app.root_path, 'static/index.html')) as f:
rv = flask.send_file(f, as_attachment=True,
attachment_filename='index.html')
value, options = \
parse_options_header(rv.headers['Content-Disposition'])
assert value == 'attachment'
rv.close()
# mimetypes + etag
assert len(captured) == 2
with app.test_request_context():
assert options['filename'] == 'index.html'
@ -737,3 +835,45 @@ class TestStreaming(object):
rv = c.get('/?name=World')
assert rv.data == b'Hello World!'
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 flask
import werkzeug
from flask._compat import text_type
@ -43,6 +44,40 @@ def test_environ_defaults():
rv = c.get('/')
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():
app = flask.Flask(__name__)
app.secret_key = 'testing'

15
tox.ini

@ -1,14 +1,20 @@
[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]
passenv = LANG
usedevelop=true
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=
pytest
pytest-cov
greenlet
redbaron
lowest: Werkzeug==0.7
lowest: Jinja2==2.4
@ -19,6 +25,7 @@ deps=
devel: git+https://github.com/pallets/jinja.git
devel: git+https://github.com/pallets/itsdangerous.git
devel: git+https://github.com/jek/blinker.git
simplejson: simplejson
[testenv:docs]
deps = sphinx

Loading…
Cancel
Save