Browse Source

Merge pull request #1 from pallets/master

update-2018-03-22
pull/2682/head
zhiguan 7 years ago committed by GitHub
parent
commit
d2f8858335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      .appveyor.yml
  2. 2
      .gitattributes
  3. 0
      .github/ISSUE_TEMPLATE.md
  4. 0
      .github/PULL_REQUEST_TEMPLATE.md
  5. 13
      .gitignore
  6. 33
      .travis.yml
  7. 45
      AUTHORS
  8. 248
      CHANGES.rst
  9. 22
      CONTRIBUTING.rst
  10. 48
      LICENSE
  11. 2
      MANIFEST.in
  12. 18
      Makefile
  13. 49
      README
  14. 63
      README.rst
  15. BIN
      docs/_static/pycharm-runconfig.png
  16. 8
      docs/advanced_foreword.rst
  17. 25
      docs/api.rst
  18. 2
      docs/changelog.rst
  19. 107
      docs/cli.rst
  20. 16
      docs/conf.py
  21. 113
      docs/config.rst
  22. 1
      docs/contents.rst.inc
  23. 2
      docs/deploying/fastcgi.rst
  24. 5
      docs/deploying/index.rst
  25. 2
      docs/deploying/wsgi-standalone.rst
  26. 4
      docs/errorhandling.rst
  27. 82
      docs/extensiondev.rst
  28. 71
      docs/extensions.rst
  29. 29
      docs/index.rst
  30. 4
      docs/installation.rst
  31. 16
      docs/patterns/appfactories.rst
  32. 2
      docs/patterns/fileuploads.rst
  33. 6
      docs/patterns/packages.rst
  34. 2
      docs/patterns/sqlite3.rst
  35. 23
      docs/python3.rst
  36. 41
      docs/quickstart.rst
  37. 16
      docs/security.rst
  38. 20
      docs/server.rst
  39. 57
      docs/testing.rst
  40. 6
      docs/tutorial/packaging.rst
  41. 9
      examples/blueprintexample/blueprintexample.py
  42. 6
      examples/blueprintexample/test_blueprintexample.py
  43. 2
      examples/flaskr/flaskr/blueprints/flaskr.py
  44. 2
      examples/flaskr/flaskr/factory.py
  45. 2
      examples/flaskr/setup.py
  46. 26
      examples/flaskr/tests/test_flaskr.py
  47. 3
      examples/jqueryexample/jqueryexample.py
  48. 2
      examples/minitwit/minitwit/minitwit.py
  49. 3
      examples/minitwit/tests/test_minitwit.py
  50. 9
      examples/patterns/largerapp/tests/test_largerapp.py
  51. 9
      examples/patterns/largerapp/yourapplication/__init__.py
  52. 9
      examples/patterns/largerapp/yourapplication/views.py
  53. 2
      flask/__init__.py
  54. 3
      flask/__main__.py
  55. 3
      flask/_compat.py
  56. 170
      flask/app.py
  57. 5
      flask/blueprints.py
  58. 214
      flask/cli.py
  59. 6
      flask/config.py
  60. 2
      flask/ctx.py
  61. 3
      flask/debughelpers.py
  62. 2
      flask/globals.py
  63. 48
      flask/helpers.py
  64. 10
      flask/json/__init__.py
  65. 3
      flask/json/tag.py
  66. 9
      flask/logging.py
  67. 77
      flask/sessions.py
  68. 3
      flask/signals.py
  69. 3
      flask/templating.py
  70. 42
      flask/testing.py
  71. 3
      flask/views.py
  72. 3
      flask/wrappers.py
  73. 309
      scripts/flaskext_tester.py
  74. 84
      scripts/make-release.py
  75. 2
      setup.cfg
  76. 97
      setup.py
  77. 3
      test-requirements.txt
  78. 47
      tests/conftest.py
  79. 4
      tests/test_appctx.py
  80. 77
      tests/test_basic.py
  81. 17
      tests/test_blueprints.py
  82. 98
      tests/test_cli.py
  83. 3
      tests/test_config.py
  84. 46
      tests/test_helpers.py
  85. 3
      tests/test_instance_config.py
  86. 9
      tests/test_json_tag.py
  87. 18
      tests/test_logging.py
  88. 2
      tests/test_regression.py
  89. 2
      tests/test_reqctx.py
  90. 2
      tests/test_signals.py
  91. 2
      tests/test_subclassing.py
  92. 2
      tests/test_templating.py
  93. 58
      tests/test_testing.py
  94. 8
      tests/test_user_error_handler.py
  95. 2
      tests/test_views.py
  96. 22
      tox.ini

23
.appveyor.yml

@ -0,0 +1,23 @@
environment:
global:
TOXENV: py
matrix:
- PYTHON: C:\Python36
- PYTHON: C:\Python27
init:
- SET PATH=%PYTHON%;%PATH%
install:
- python -m pip install -U pip setuptools wheel tox
build: false
test_script:
- python -m tox
branches:
only:
- master
- /^.*-maintenance$/

2
.gitattributes vendored

@ -1 +1 @@
CHANGES merge=union CHANGES.rst merge=union

0
.github/ISSUE_TEMPLATE.rst → .github/ISSUE_TEMPLATE.md

0
.github/PULL_REQUEST_TEMPLATE.rst → .github/PULL_REQUEST_TEMPLATE.md

13
.gitignore vendored

@ -3,19 +3,20 @@
.flaskenv .flaskenv
*.pyc *.pyc
*.pyo *.pyo
env env/
env* env*
dist dist/
build build/
*.egg *.egg
*.egg-info *.egg-info/
_mailinglist _mailinglist
.tox .tox/
.cache/ .cache/
.pytest_cache/
.idea/ .idea/
# Coverage reports # Coverage reports
htmlcov htmlcov/
.coverage .coverage
.coverage.* .coverage.*
*,cover *,cover

33
.travis.yml

@ -1,28 +1,41 @@
os: linux
sudo: false sudo: false
language: python language: python
matrix: matrix:
include: include:
- python: 3.6 - python: 3.6
env: TOXENV=py,codecov env: TOXENV=py,simplejson,devel,lowest,codecov
- python: 3.6
env: TOXENV=docs-html
- python: 3.5 - python: 3.5
env: TOXENV=py,codecov env: TOXENV=py,codecov
- python: 3.4 - python: 3.4
env: TOXENV=py,codecov env: TOXENV=py,codecov
- python: 3.3
env: TOXENV=py,codecov
- python: 2.7 - python: 2.7
env: TOXENV=py,codecov env: TOXENV=py,simplejson,devel,lowest,codecov
- python: 2.6
env: TOXENV=py,codecov
- python: pypy - python: pypy
env: TOXENV=py,codecov env: TOXENV=py,codecov
- python: nightly - python: nightly
env: TOXENV=py env: TOXENV=py
- python: 3.6 - os: osx
env: TOXENV=docs-html language: generic
- python: 3.6 env: TOXENV=py
env: TOXENV=py-simplejson,codecov allow_failures:
- python: nightly
env: TOXENV=py
- os: osx
language: generic
env: TOXENV=py
fast_finish: true
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update;
brew install python3 redis memcached;
virtualenv -p python3 ~/py-env;
. ~/py-env/bin/activate;
fi
install: install:
- pip install tox - pip install tox

45
AUTHORS

@ -1,39 +1,12 @@
Flask is written and maintained by Armin Ronacher and Flask is developed and maintained by the Pallets team and community
various contributors: contributors. It was created by Armin Ronacher. The core maintainers
are:
Development Lead - David Lord (davidism)
```````````````` - Adrian Mönnich (ThiefMaster)
- Armin Ronacher (mitsuhiko)
- Marcus Unterwaditzer (untitaker)
- Armin Ronacher <armin.ronacher@active-4.com> A full list of contributors is available from git with::
Patches and Suggestions git shortlog -sne
```````````````````````
- Adam Byrtek
- Adam Zapletal
- Ali Afshar
- Chris Edgemon
- Chris Grindstaff
- Christopher Grebs
- Daniel Neuhäuser
- Dan Sully
- David Lord @davidism
- Edmond Burnett
- Florent Xicluna
- Georg Brandl
- Jeff Widman @jeffwidman
- Joshua Bronson @jab
- Justin Quick
- Kenneth Reitz
- Keyan Pishdadian
- Marian Sigler
- Martijn Pieters
- Matt Campell
- Matthew Frazier
- Michael van Tellingen
- Ron DuPlain
- Sebastien Estienne
- Simon Sapin
- Stephane Wirtel
- Thomas Schranz
- Zhao Xiaohong

248
CHANGES → CHANGES.rst

@ -1,121 +1,155 @@
.. currentmodule:: flask
Flask Changelog Flask Changelog
=============== ===============
Here you can see the full list of changes between each Flask release.
Version 0.13 Version 1.0
------------ -----------
Major release, unreleased unreleased
- Minimum Werkzeug version bumped to 0.9, but please use the latest version. - **Python 2.6 and 3.3 are no longer supported.** (`pallets/meta#24`_)
- Minimum Click version bumped to 4, but please use the latest version. - Bump minimum dependency versions to the latest stable versions:
- Make ``app.run()`` into a noop if a Flask application is run from the Werkzeug >= 0.14, Jinja >= 2.10, itsdangerous >= 0.24, Click >= 5.1.
development server on the command line. This avoids some behavior that (`#2586`_)
was confusing to debug for newcomers. - Skip :meth:`app.run <Flask.run>` when a Flask application is run from
- Change default configuration ``JSONIFY_PRETTYPRINT_REGULAR=False``. the command line. This avoids some behavior that was confusing to
``jsonify()`` method returns compressed response by default, and pretty debug.
response in debug mode. (`#2193`_) - Change the default for :data:`JSONIFY_PRETTYPRINT_REGULAR` to
- Change ``Flask.__init__`` to accept two new keyword arguments, ``False``. :func:`~json.jsonify` returns a compact format by default,
``host_matching`` and ``static_host``. This enables ``host_matching`` to be and an indented format in debug mode. (`#2193`_)
set properly by the time the constructor adds the static route, and enables - :meth:`Flask.__init__ <Flask>` accepts the ``host_matching`` argument
the static route to be properly associated with the required host. and sets it on :attr:`~Flask.url_map`. (`#1559`_)
(``#1559``) - :meth:`Flask.__init__ <Flask>` accepts the ``static_host`` argument
- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_) and passes it as the ``host`` argument when defining the static route.
- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``. (`#1559`_)
(`#2017`_) - :func:`send_file` supports Unicode in ``attachment_filename``.
- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable (`#2223`_)
adding OPTIONS method when the ``view_func`` argument is not a class. - Pass ``_scheme`` argument from :func:`url_for` to
(`#1489`_). :meth:`~Flask.handle_url_build_error`. (`#2017`_)
- ``MethodView`` can inherit method handlers from base classes. (`#1936`_) - :meth:`~Flask.add_url_rule` accepts the ``provide_automatic_options``
- Errors caused while opening the session at the beginning of the request are argument to disable adding the ``OPTIONS`` method. (`#1489`_)
handled by the app's error handlers. (`#2254`_) - :class:`~views.MethodView` subclasses inherit method handlers from
- Blueprints gained ``json_encoder`` and ``json_decoder`` attributes to base classes. (`#1936`_)
override the app's encoder and decoder. (`#1898`_) - Errors caused while opening the session at the beginning of the
- ``Flask.make_response`` raises ``TypeError`` instead of ``ValueError`` for request are handled by the app's error handlers. (`#2254`_)
bad response types. The error messages have been improved to describe why the - Blueprints gained :attr:`~Blueprint.json_encoder` and
type is invalid. (`#2256`_) :attr:`~Blueprint.json_decoder` attributes to override the app's
- Add ``routes`` CLI command to output routes registered on the application. encoder and decoder. (`#1898`_)
(`#2259`_) - :meth:`Flask.make_response` raises ``TypeError`` instead of
``ValueError`` for bad response types. The error messages have been
improved to describe why the type is invalid. (`#2256`_)
- Add ``routes`` CLI command to output routes registered on the
application. (`#2259`_)
- Show warning when session cookie domain is a bare hostname or an IP - Show warning when session cookie domain is a bare hostname or an IP
address, as these may not behave properly in some browsers, such as Chrome. address, as these may not behave properly in some browsers, such as
(`#2282`_) Chrome. (`#2282`_)
- Allow IP address as exact session cookie domain. (`#2282`_) - Allow IP address as exact session cookie domain. (`#2282`_)
- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``. - ``SESSION_COOKIE_DOMAIN`` is set if it is detected through
(`#2282`_) ``SERVER_NAME``. (`#2282`_)
- Auto-detect zero-argument app factory called ``create_app`` or ``make_app`` - Auto-detect zero-argument app factory called ``create_app`` or
from ``FLASK_APP``. (`#2297`_) ``make_app`` from ``FLASK_APP``. (`#2297`_)
- Factory functions are not required to take a ``script_info`` parameter to - Factory functions are not required to take a ``script_info`` parameter
work with the ``flask`` command. If they take a single parameter or a to work with the ``flask`` command. If they take a single parameter or
parameter named ``script_info``, the ``ScriptInfo`` object will be passed. a parameter named ``script_info``, the :class:`~cli.ScriptInfo` object
(`#2319`_) will be passed. (`#2319`_)
- FLASK_APP=myproject.app:create_app('dev') support. - ``FLASK_APP`` can be set to an app factory, with arguments if needed,
- ``FLASK_APP`` can be set to an app factory, with arguments if needed, for for example ``FLASK_APP=myproject.app:create_app('dev')``. (`#2326`_)
example ``FLASK_APP=myproject.app:create_app('dev')``. (`#2326`_) - ``FLASK_APP`` can point to local packages that are not installed in
- ``FLASK_APP`` can point to local packages that are not installed in dev mode, editable mode, although ``pip install -e`` is still preferred.
although `pip install -e` should still be preferred. (`#2414`_) (`#2414`_)
- ``View.provide_automatic_options = True`` is set on the view function from - The :class:`~views.View` class attribute
``View.as_view``, to be detected in ``app.add_url_rule``. (`#2316`_) :attr:`~views.View.provide_automatic_options` is set in
:meth:`~views.View.as_view`, to be detected by
:meth:`~Flask.add_url_rule`. (`#2316`_)
- Error handling will try handlers registered for ``blueprint, code``, - Error handling will try handlers registered for ``blueprint, code``,
``app, code``, ``blueprint, exception``, ``app, exception``. (`#2314`_) ``app, code``, ``blueprint, exception``, ``app, exception``.
- ``Cookie`` is added to the response's ``Vary`` header if the session is (`#2314`_)
accessed at all during the request (and it wasn't deleted). (`#2288`_) - ``Cookie`` is added to the response's ``Vary`` header if the session
- ``app.test_request_context()`` take ``subdomain`` and ``url_scheme`` is accessed at all during the request (and not deleted). (`#2288`_)
parameters for use when building base URL. (`#1621`_) - :meth:`~Flask.test_request_context` accepts ``subdomain`` and
- Set ``APPLICATION_ROOT = '/'`` by default. This was already the implicit ``url_scheme`` arguments for use when building the base URL.
default when it was set to ``None``. (`#1621`_)
- ``TRAP_BAD_REQUEST_ERRORS`` is enabled by default in debug mode. - Set :data:`APPLICATION_ROOT` to ``'/'`` by default. This was already
``BadRequestKeyError`` has a message with the bad key in debug mode instead the implicit default when it was set to ``None``.
of the generic bad request message. (`#2348`_) - :data:`TRAP_BAD_REQUEST_ERRORS` is enabled by default in debug mode.
- Allow registering new tags with ``TaggedJSONSerializer`` to support ``BadRequestKeyError`` has a message with the bad key in debug mode
storing other types in the session cookie. (`#2352`_) instead of the generic bad request message. (`#2348`_)
- Only open the session if the request has not been pushed onto the context - Allow registering new tags with
stack yet. This allows ``stream_with_context`` generators to access the same :class:`~json.tag.TaggedJSONSerializer` to support storing other types
session that the containing view uses. (`#2354`_) in the session cookie. (`#2352`_)
- Add ``json`` keyword argument for the test client request methods. This will - Only open the session if the request has not been pushed onto the
dump the given object as JSON and set the appropriate content type. context stack yet. This allows :func:`~stream_with_context`
(`#2358`_) generators to access the same session that the containing view uses.
- Extract JSON handling to a mixin applied to both the request and response (`#2354`_)
classes used by Flask. This adds the ``is_json`` and ``get_json`` methods to - Add ``json`` keyword argument for the test client request methods.
the response to make testing JSON response much easier. (`#2358`_) This will dump the given object as JSON and set the appropriate
- Removed error handler caching because it caused unexpected results for some content type. (`#2358`_)
exception inheritance hierarchies. Register handlers explicitly for each - Extract JSON handling to a mixin applied to both the :class:`Request`
exception if you don't want to traverse the MRO. (`#2362`_) and :class:`Response` classes. This adds the :meth:`~Response.is_json`
and :meth:`~Response.get_json` methods to the response to make testing
JSON response much easier. (`#2358`_)
- Removed error handler caching because it caused unexpected results for
some exception inheritance hierarchies. Register handlers explicitly
for each exception if you want to avoid traversing the MRO. (`#2362`_)
- Fix incorrect JSON encoding of aware, non-UTC datetimes. (`#2374`_) - Fix incorrect JSON encoding of aware, non-UTC datetimes. (`#2374`_)
- Template auto reloading will honor the ``run`` command's ``debug`` flag even - Template auto reloading will honor debug mode even even if
if ``app.jinja_env`` was already accessed. (`#2373`_) :attr:`~Flask.jinja_env` was already accessed. (`#2373`_)
- The following old deprecated code was removed. (`#2385`_) - The following old deprecated code was removed. (`#2385`_)
- ``flask.ext`` - import extensions directly by their name instead of - ``flask.ext`` - import extensions directly by their name instead of
through the ``flask.ext`` namespace. For example, through the ``flask.ext`` namespace. For example,
``import flask.ext.sqlalchemy`` becomes ``import flask_sqlalchemy``. ``import flask.ext.sqlalchemy`` becomes ``import flask_sqlalchemy``.
- ``Flask.init_jinja_globals`` - extend ``Flask.create_jinja_environment`` - ``Flask.init_jinja_globals`` - extend
instead. :meth:`Flask.create_jinja_environment` instead.
- ``Flask.error_handlers`` - tracked by ``Flask.error_handler_spec``, - ``Flask.error_handlers`` - tracked by
use ``@app.errorhandler`` to register handlers. :attr:`Flask.error_handler_spec`, use :meth:`Flask.errorhandler` to
- ``Flask.request_globals_class`` - use ``Flask.app_ctx_globals_class`` register handlers.
instead. - ``Flask.request_globals_class`` - use
- ``Flask.static_path`` - use ``Flask.static_url_path`` instead. :attr:`Flask.app_ctx_globals_class` instead.
- ``Request.module`` - use ``Request.blueprint`` instead. - ``Flask.static_path`` - use :attr:`Flask.static_url_path` instead.
- ``Request.module`` - use :attr:`Request.blueprint` instead.
- The ``request.json`` property is no longer deprecated. (`#1421`_)
- Support passing an existing ``EnvironBuilder`` or ``dict`` to - The :attr:`Request.json` property is no longer deprecated. (`#1421`_)
``test_client.open``. (`#2412`_) - Support passing a :class:`~werkzeug.test.EnvironBuilder` or
- The ``flask`` command and ``app.run`` will load environment variables using ``dict`` to :meth:`test_client.open <werkzeug.test.Client.open>`.
from ``.env`` and ``.flaskenv`` files if python-dotenv is installed. (`#2412`_)
(`#2416`_) - The ``flask`` command and :meth:`Flask.run` will load environment
- When passing a full URL to the test client, use the scheme in the URL instead variables from ``.env`` and ``.flaskenv`` files if python-dotenv is
of the ``PREFERRED_URL_SCHEME``. (`#2430`_) installed. (`#2416`_)
- ``app.logger`` has been simplified. ``LOGGER_NAME`` and - When passing a full URL to the test client, the scheme in the URL is
``LOGGER_HANDLER_POLICY`` config was removed. The logger is always named used instead of :data:`PREFERRED_URL_SCHEME`. (`#2430`_)
``flask.app``. The level is only set on first access, it doesn't check - :attr:`Flask.logger` has been simplified. ``LOGGER_NAME`` and
``app.debug`` each time. Only one format is used, not different ones ``LOGGER_HANDLER_POLICY`` config was removed. The logger is always
depending on ``app.debug``. No handlers are removed, and a handler is only named ``flask.app``. The level is only set on first access, it doesn't
added if no handlers are already configured. (`#2436`_) check :attr:`Flask.debug` each time. Only one format is used, not
- Blueprint view function name may not contain dots. (`#2450`_) different ones depending on :attr:`Flask.debug`. No handlers are
removed, and a handler is only added if no handlers are already
configured. (`#2436`_)
- Blueprint view function names may not contain dots. (`#2450`_)
- Fix a ``ValueError`` caused by invalid ``Range`` requests in some
cases. (`#2526`_)
- The development server uses threads by default. (`#2529`_)
- Loading config files with ``silent=True`` will ignore
:data:`~errno.ENOTDIR` errors. (`#2581`_)
- Pass ``--cert`` and ``--key`` options to ``flask run`` to run the
development server over HTTPS. (`#2606`_)
- Added :data:`SESSION_COOKIE_SAMESITE` to control the ``SameSite``
attribute on the session cookie. (`#2607`_)
- Added :meth:`~flask.Flask.test_cli_runner` to create a Click runner
that can invoke Flask CLI commands for testing. (`#2636`_)
- Subdomain matching is disabled by default and setting
:data:`SERVER_NAME` does not implicily enable it. It can be enabled by
passing ``subdomain_matching=True`` to the ``Flask`` constructor.
(`#2635`_)
- A single trailing slash is stripped from the blueprint ``url_prefix``
when it is registered with the app. (`#2629`_)
.. _pallets/meta#24: https://github.com/pallets/meta/issues/24
.. _#1421: https://github.com/pallets/flask/issues/1421 .. _#1421: https://github.com/pallets/flask/issues/1421
.. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1489: https://github.com/pallets/flask/pull/1489
.. _#1559: https://github.com/pallets/flask/issues/1559
.. _#1621: https://github.com/pallets/flask/pull/1621 .. _#1621: https://github.com/pallets/flask/pull/1621
.. _#1898: https://github.com/pallets/flask/pull/1898 .. _#1898: https://github.com/pallets/flask/pull/1898
.. _#1936: https://github.com/pallets/flask/pull/1936 .. _#1936: https://github.com/pallets/flask/pull/1936
@ -146,6 +180,16 @@ Major release, unreleased
.. _#2430: https://github.com/pallets/flask/pull/2430 .. _#2430: https://github.com/pallets/flask/pull/2430
.. _#2436: https://github.com/pallets/flask/pull/2436 .. _#2436: https://github.com/pallets/flask/pull/2436
.. _#2450: https://github.com/pallets/flask/pull/2450 .. _#2450: https://github.com/pallets/flask/pull/2450
.. _#2526: https://github.com/pallets/flask/issues/2526
.. _#2529: https://github.com/pallets/flask/pull/2529
.. _#2586: https://github.com/pallets/flask/issues/2586
.. _#2581: https://github.com/pallets/flask/pull/2581
.. _#2606: https://github.com/pallets/flask/pull/2606
.. _#2607: https://github.com/pallets/flask/pull/2607
.. _#2636: https://github.com/pallets/flask/pull/2636
.. _#2635: https://github.com/pallets/flask/pull/2635
.. _#2629: https://github.com/pallets/flask/pull/2629
Version 0.12.2 Version 0.12.2
-------------- --------------

22
CONTRIBUTING.rst

@ -109,8 +109,8 @@ depends on which part of Flask you're working on. Travis-CI will run the full
suite when you submit your pull request. suite when you submit your pull request.
The full test suite takes a long time to run because it tests multiple The full test suite takes a long time to run because it tests multiple
combinations of Python and dependencies. You need to have Python 2.6, 2.7, 3.3, combinations of Python and dependencies. You need to have Python 2.7, 3.4,
3.4, 3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run:: 3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run::
tox tox
@ -131,8 +131,22 @@ Read more about `coverage <https://coverage.readthedocs.io>`_.
Running the full test suite with ``tox`` will combine the coverage reports Running the full test suite with ``tox`` will combine the coverage reports
from all runs. from all runs.
``make`` targets
~~~~~~~~~~~~~~~~ Building the docs
~~~~~~~~~~~~~~~~~
Build the docs in the ``docs`` directory using Sphinx::
cd docs
make html
Open ``_build/html/index.html`` in your browser to view the docs.
Read more about `Sphinx <http://www.sphinx-doc.org>`_.
make targets
~~~~~~~~~~~~
Flask provides a ``Makefile`` with various shortcuts. They will ensure that Flask provides a ``Makefile`` with various shortcuts. They will ensure that
all dependencies are installed. all dependencies are installed.

48
LICENSE

@ -1,33 +1,31 @@
Copyright (c) 2015 by Armin Ronacher and contributors. See AUTHORS Copyright © 2010 by the Pallets team.
for more details.
Some rights reserved. Some rights reserved.
Redistribution and use in source and binary forms of the software as well Redistribution and use in source and binary forms of the software as
as documentation, with or without modification, are permitted provided well as documentation, with or without modification, are permitted
that the following conditions are met: provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright notice,
notice, this list of conditions and the following disclaimer. this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above * Redistributions in binary form must reproduce the above copyright
copyright notice, this list of conditions and the following notice, this list of conditions and the following disclaimer in the
disclaimer in the documentation and/or other materials provided documentation and/or other materials provided with the distribution.
with the distribution.
* The names of the contributors may not be used to endorse or * Neither the name of the copyright holder nor the names of its
promote products derived from this software without specific contributors may be used to endorse or promote products derived from
prior written permission. this software without specific prior written permission.
THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
DAMAGE. SUCH DAMAGE.

2
MANIFEST.in

@ -1,4 +1,4 @@
include Makefile CHANGES LICENSE AUTHORS include Makefile CHANGES.rst LICENSE AUTHORS tox.ini
graft artwork graft artwork
graft tests graft tests

18
Makefile

@ -9,7 +9,6 @@ test: clean-pyc install-dev
pytest pytest
coverage: clean-pyc install-dev coverage: clean-pyc install-dev
pip install -q -e .[test]
coverage run -m pytest coverage run -m pytest
coverage report coverage report
coverage html coverage html
@ -34,20 +33,3 @@ clean-pyc:
find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} + find . -name '*~' -exec rm -f {} +
upload-docs:
$(MAKE) -C docs html dirhtml latex epub
$(MAKE) -C docs/_build/latex all-pdf
cd docs/_build/; mv html flask-docs; zip -r flask-docs.zip flask-docs; mv flask-docs html
rsync -a docs/_build/dirhtml/ flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/
rsync -a docs/_build/latex/Flask.pdf flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/flask-docs.pdf
rsync -a docs/_build/flask-docs.zip flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/flask-docs.zip
rsync -a docs/_build/epub/Flask.epub flow.srv.pocoo.org:/srv/websites/flask.pocoo.org/docs/flask-docs.epub
# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html
ebook:
@echo 'Using .epub from `make upload-docs` to create .mobi.'
@echo 'Command `ebook-covert` is provided by calibre package.'
@echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).'
@echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.'
ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher'

49
README

@ -1,49 +0,0 @@
// Flask //
web development, one drop at a time
~ What is Flask?
Flask is a microframework for Python based on Werkzeug
and Jinja2. It's intended for getting started very quickly
and was developed with best intentions in mind.
~ Is it ready?
It's still not 1.0 but it's shaping up nicely and is
already widely used. Consider the API to slightly
improve over time but we don't plan on breaking it.
~ What do I need?
All dependencies are installed by using `pip install Flask`.
We encourage you to use a virtualenv. Check the docs for
complete installation and usage instructions.
~ Where are the docs?
Go to http://flask.pocoo.org/docs/ for a prebuilt version
of the current documentation. Otherwise build them yourself
from the sphinx sources in the docs folder.
~ Where are the tests?
Good that you're asking. The tests are in the
tests/ folder. To run the tests use the
`pytest` testing tool:
$ pytest
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
ask on the mailinglist: http://flask.pocoo.org/mailinglist/
See http://flask.pocoo.org/community/ for more resources.

63
README.rst

@ -0,0 +1,63 @@
Flask
=====
Flask is a lightweight `WSGI`_ web application framework. It is designed
to make getting started quick and easy, with the ability to scale up to
complex applications. It began as a simple wrapper around `Werkzeug`_
and `Jinja`_ and has become one of the most popular Python web
application frameworks.
Flask offers suggestions, but doesn't enforce any dependencies or
project layout. It is up to the developer to choose the tools and
libraries they want to use. There are many extensions provided by the
community that make adding new functionality easy.
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U Flask
A Simple Example
----------------
.. code-block:: python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
.. code-block:: none
$ FLASK_APP=hello.py flask run
* Running on http://localhost:5000/
Links
-----
* Website: https://www.palletsprojects.com/p/flask/
* Documentation: http://flask.pocoo.org/docs/
* Releases: https://pypi.org/project/Flask/
* Code: https://github.com/pallets/flask
* Issue tracker: https://github.com/pallets/flask/issues
* Test status:
* Linux, Mac: https://travis-ci.org/pallets/flask
* Windows: https://ci.appveyor.com/project/pallets/flask
* Test coverage: https://codecov.io/gh/pallets/flask
.. _WSGI: https://wsgi.readthedocs.io
.. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/
.. _Jinja: https://www.palletsprojects.com/p/jinja/
.. _pip: https://pip.pypa.io/en/stable/quickstart/

BIN
docs/_static/pycharm-runconfig.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 17 KiB

8
docs/advanced_foreword.rst

@ -45,11 +45,3 @@ spam, links to malicious software, and the like.
Flask is no different from any other framework in that you the developer must Flask is no different from any other framework in that you the developer must
build with caution, watching for exploits when building to your requirements. build with caution, watching for exploits when building to your requirements.
Python 3 Support in Flask
-------------------------
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`.

25
docs/api.rst

@ -188,6 +188,15 @@ Test Client
:members: :members:
Test CLI Runner
---------------
.. currentmodule:: flask.testing
.. autoclass:: FlaskCliRunner
:members:
Application Globals Application Globals
------------------- -------------------
@ -361,22 +370,6 @@ Configuration
.. autoclass:: Config .. autoclass:: Config
:members: :members:
Extensions
----------
.. data:: flask.ext
This module acts as redirect import module to Flask extensions. It was
added in 0.8 as the canonical way to import Flask extensions and makes
it possible for us to have more flexibility in how we distribute
extensions.
If you want to use an extension named “Flask-Foo” you would import it
from :data:`~flask.ext` as follows::
from flask.ext import foo
.. versionadded:: 0.8
Stream Helpers Stream Helpers
-------------- --------------

2
docs/changelog.rst

@ -1 +1 @@
.. include:: ../CHANGES .. include:: ../CHANGES.rst

107
docs/cli.rst

@ -117,23 +117,41 @@ context will be active, and the app instance will be imported. ::
Use :meth:`~Flask.shell_context_processor` to add other automatic imports. Use :meth:`~Flask.shell_context_processor` to add other automatic imports.
Debug Mode Environments
---------- ------------
.. versionadded:: 1.0
The environment in which the Flask app runs is set by the
:envvar:`FLASK_ENV` environment variable. If not set it defaults to
``production``. The other recognized environment is ``development``.
Flask and extensions may choose to enable behaviors based on the
environment.
Set the :envvar:`FLASK_DEBUG` environment variable to override the If the env is set to ``development``, the ``flask`` command will enable
application's :attr:`~Flask.debug` flag. The value ``1`` enables it, ``0`` debug mode and ``flask run`` will enable the interactive debugger and
disables it. Forcing the debug flag on also enables the debugger and reloader reloader.
when running the development server. ::
$ FLASK_DEBUG=1 flask run ::
$ FLASK_ENV=development flask run
* Serving Flask app "hello" * Serving Flask app "hello"
* Forcing debug mode on * Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with inotify reloader * Restarting with inotify reloader
* Debugger is active! * Debugger is active!
* Debugger PIN: 223-456-919 * Debugger PIN: 223-456-919
Debug Mode
----------
Debug mode will be enabled when :envvar:`FLASK_ENV` is ``development``,
as described above. If you want to control debug mode separately, use
:envvar:`FLASK_DEBUG`. The value ``1`` enables it, ``0`` disables it.
.. _dotenv: .. _dotenv:
Environment Variables From dotenv Environment Variables From dotenv
@ -176,7 +194,7 @@ Unix Bash, :file:`venv/bin/activate`::
export FLASK_APP=hello export FLASK_APP=hello
Windows CMD, :file:`venv\Scripts\activate.bat`:: Windows CMD, :file:`venv\\Scripts\\activate.bat`::
set FLASK_APP=hello set FLASK_APP=hello
@ -229,6 +247,9 @@ group. This is useful if you want to organize multiple related commands. ::
flask user create demo flask user create demo
See :ref:`testing-cli` for an overview of how to test your custom
commands.
Application Context Application Context
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@ -337,47 +358,67 @@ script is available. Note that you don't need to set ``FLASK_APP``. ::
$ pip install -e . $ pip install -e .
$ wiki run $ wiki run
.. admonition:: Errors in Custom Scripts
When using a custom script, if you introduce an error in your
module-level code, the reloader will fail because it can no longer
load the entry point.
The ``flask`` command, being separate from your code, does not have
this issue and is recommended in most cases.
.. _console script: https://packaging.python.org/tutorials/distributing-packages/#console-scripts .. _console script: https://packaging.python.org/tutorials/distributing-packages/#console-scripts
PyCharm Integration PyCharm Integration
------------------- -------------------
The new Flask CLI features aren't yet fully integrated into the PyCharm IDE, Prior to PyCharm 2018.1, the Flask CLI features weren't yet fully
so we have to do a few tweaks to get them working smoothly. These instructions integrated into PyCharm. We have to do a few tweaks to get them working
should be similar for any other IDE you might want to use. smoothly. These instructions should be similar for any other IDE you
might want to use.
In PyCharm, with your project open, click on *Run* from the menu bar and go to In PyCharm, with your project open, click on *Run* from the menu bar and
*Edit Configurations*. You'll be greeted by a screen similar to this: go to *Edit Configurations*. You'll be greeted by a screen similar to
this:
.. image:: _static/pycharm-runconfig.png .. image:: _static/pycharm-runconfig.png
:align: center :align: center
:class: screenshot :class: screenshot
:alt: screenshot of pycharm's run configuration settings :alt: screenshot of pycharm's run configuration settings
There's quite a few options to change, but once we've done it for one command, There's quite a few options to change, but once we've done it for one
we can easily copy the entire configuration and make a single tweak to give us command, we can easily copy the entire configuration and make a single
access to other commands, including any custom ones you may implement yourself. tweak to give us access to other commands, including any custom ones you
may implement yourself.
Click the + (*Add New Configuration*) button and select *Python*. Give
the configuration a good descriptive name such as "Run Flask Server".
For the ``flask run`` command, check "Single instance only" since you
can't run the server more than once at the same time.
Select *Module name* from the dropdown (**A**) then input ``flask``.
For the *Script* input (**A**), navigate to your project's virtual environment. The *Parameters* field (**B**) is set to the CLI command to execute
Within that folder, pick the ``flask`` executable which will reside in the (with any arguments). In this example we use ``run``, which will run
``bin`` folder, or in the ``Scripts`` on Windows. the development server.
The *Script Parameter* field (**B**) is set to the CLI command you to execute. You can skip this next step if you're using :ref:`dotenv`. We need to
In this example we use ``run``, which will run the development server. add an environment variable (**C**) to identify our application. Click
on the browse button and add an entry with ``FLASK_APP`` on the left and
the Python import or file on the right (``hello`` for example).
You can skip this next step if you're using :ref:`dotenv`. We need to add an Next we need to set the working directory (**D**) to be the folder where
environment variable (**C**) to identify our application. Click on the browse our application resides.
button and add an entry with ``FLASK_APP`` on the left and the name of the
Python file or package on the right (``app.py`` for example).
Next we need to set the working directory (**D**) to be the same folder where If you have installed your project as a package in your virtualenv, you
our application file or package resides. PyCharm changed it to the directory may untick the *PYTHONPATH* options (**E**). This will more accurately
with the ``flask`` executable when we selected it earlier, which is incorrect. match how you deploy the app later.
Finally, untick the *PYTHONPATH* options (**E**) and give the configuration a Click *Apply* to save the configuration, or *OK* to save and close the
good descriptive name, such as "Run Flask Server", and click *Apply*. window. Select the configuration in the main PyCharm window and click
the play button next to it to run the server.
Now that we have a configuration which runs ``flask run`` from within PyCharm, Now that we have a configuration which runs ``flask run`` from within
we can simply copy that configuration and alter the *Script* argument PyCharm, we can copy that configuration and alter the *Script* argument
to run a different CLI command, e.g. ``flask shell``. to run a different CLI command, e.g. ``flask shell``.

16
docs/conf.py

@ -17,6 +17,8 @@ import pkg_resources
import time import time
import datetime import datetime
from sphinx.application import Sphinx
BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
@ -296,3 +298,17 @@ def unwrap_decorators():
unwrap_decorators() unwrap_decorators()
del unwrap_decorators del unwrap_decorators
def setup(app: Sphinx):
def cut_module_meta(app, what, name, obj, options, lines):
"""Remove metadata from autodoc output."""
if what != 'module':
return
lines[:] = [
line for line in lines
if not line.startswith((':copyright:', ':license:'))
]
app.connect('autodoc-process-docstring', cut_module_meta)

113
docs/config.rst

@ -20,6 +20,7 @@ object. This is the place where Flask itself puts certain configuration
values and also where extensions can put their configuration values. But values and also where extensions can put their configuration values. But
this is also where you can have your own configuration. this is also where you can have your own configuration.
Configuration Basics Configuration Basics
-------------------- --------------------
@ -27,50 +28,92 @@ The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and
can be modified just like any dictionary:: can be modified just like any dictionary::
app = Flask(__name__) app = Flask(__name__)
app.config['DEBUG'] = True app.config['TESTING'] = True
Certain configuration values are also forwarded to the Certain configuration values are also forwarded to the
:attr:`~flask.Flask` object so you can read and write them from there:: :attr:`~flask.Flask` object so you can read and write them from there::
app.debug = True app.testing = True
To update multiple keys at once you can use the :meth:`dict.update` To update multiple keys at once you can use the :meth:`dict.update`
method:: method::
app.config.update( app.config.update(
DEBUG=True, TESTING=True,
SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/' SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/'
) )
.. admonition:: Debug Mode with the ``flask`` Script
If you use the :command:`flask` script to start a local development Environment and Debug Features
server, to enable the debug mode, you need to export the ``FLASK_DEBUG`` ------------------------------
environment variable before running the server::
The :data:`ENV` and :data:`DEBUG` config values are special because they
may behave inconsistently if changed after the app has begun setting up.
In order to set the environment and debug mode reliably, Flask uses
environment variables.
The environment is used to indicate to Flask, extensions, and other
programs, like Sentry, what context Flask is running in. It is
controlled with the :envvar:`FLASK_ENV` environment variable and
defaults to ``production``.
Setting :envvar:`FLASK_ENV` to ``development`` will enable debug mode.
``flask run`` will use the interactive debugger and reloader by default
in debug mode. To control this separately from the environment, use the
:envvar:`FLASK_DEBUG` flag.
.. versionchanged:: 1.0
Added :envvar:`FLASK_ENV` to control the environment separately
from debug mode. The development environment enables debug mode.
To switch Flask to the development environment and enable debug mode,
set :envvar:`FLASK_ENV`::
$ export FLASK_DEBUG=1 $ export FLASK_ENV=development
$ flask run $ flask run
(On Windows you need to use ``set`` instead of ``export``). (On Windows, use ``set`` instead of ``export``.)
Using the environment variables as described above is recommended. While
it is possible to set :data:`ENV` and :data:`DEBUG` in your config or
code, this is strongly discouraged. They can't be read early by the
``flask`` command, and some systems or extensions may have already
configured themselves based on a previous value.
``app.debug`` and ``app.config['DEBUG']`` are not compatible with
  the :command:`flask` script. They only worked when using ``Flask.run()``
method.
Builtin Configuration Values Builtin Configuration Values
---------------------------- ----------------------------
The following configuration values are used internally by Flask: The following configuration values are used internally by Flask:
.. py:data:: ENV
What environment the app is running in. Flask and extensions may
enable behaviors based on the environment, such as enabling debug
mode. The :attr:`~flask.Flask.env` attribute maps to this config
key. This is set by the :envvar:`FLASK_ENV` environment variable and
may not behave as expected if set in code.
**Do not enable development when deploying in production.**
Default: ``'production'``
.. versionadded:: 1.0
.. py:data:: DEBUG .. py:data:: DEBUG
Enable debug mode. When using the development server with ``flask run`` or Whether debug mode is enabled. When using ``flask run`` to start the
``app.run``, an interactive debugger will be shown for unhanlded development server, an interactive debugger will be shown for
exceptions, and the server will be reloaded when code changes. unhandled exceptions, and the server will be reloaded when code
changes. The :attr:`~flask.Flask.debug` attribute maps to this
config key. This is enabled when :data:`ENV` is ``'development'``
and is overridden by the ``FLASK_DEBUG`` environment variable. It
may not behave as expected if set in code.
**Do not enable debug mode in production.** **Do not enable debug mode when deploying in production.**
Default: ``False`` Default: ``True`` if :data:`ENV` is ``'production'``, or ``False``
otherwise.
.. py:data:: TESTING .. py:data:: TESTING
@ -104,7 +147,7 @@ The following configuration values are used internally by Flask:
Default: ``False`` Default: ``False``
.. py:data:: TRAP_BAD_REQUEST_ERRORS`` .. py:data:: TRAP_BAD_REQUEST_ERRORS
Trying to access a key that doesn't exist from request dicts like ``args`` Trying to access a key that doesn't exist from request dicts like ``args``
and ``form`` will return a 400 Bad Request error page. Enable this to treat and ``form`` will return a 400 Bad Request error page. Enable this to treat
@ -138,8 +181,8 @@ The following configuration values are used internally by Flask:
.. py:data:: SESSION_COOKIE_DOMAIN .. py:data:: SESSION_COOKIE_DOMAIN
The domain match rule that the session cookie will be valid for. If not The domain match rule that the session cookie will be valid for. If not
set, the cookie will be valid for all subdomains of ``SERVER_NAME``. If set, the cookie will be valid for all subdomains of :data:`SERVER_NAME`.
``False``, the cookie's domain will not be set. If ``False``, the cookie's domain will not be set.
Default: ``None`` Default: ``None``
@ -165,6 +208,16 @@ The following configuration values are used internally by Flask:
Default: ``False`` Default: ``False``
.. py:data:: SESSION_COOKIE_SAMESITE
Restrict how cookies are sent with requests from external sites. Can
be set to ``'Lax'`` (recommended) or ``'Strict'``.
See :ref:`security-cookie`.
Default: ``None``
.. versionadded:: 1.0
.. py:data:: PERMANENT_SESSION_LIFETIME .. py:data:: PERMANENT_SESSION_LIFETIME
If ``session.permanent`` is true, the cookie's expiration will be set this If ``session.permanent`` is true, the cookie's expiration will be set this
@ -204,13 +257,14 @@ The following configuration values are used internally by Flask:
.. py:data:: SERVER_NAME .. py:data:: SERVER_NAME
Inform the application what host and port it is bound to. Required for Inform the application what host and port it is bound to. Required
subdomain route matching support. for subdomain route matching support.
If set, will be used for the session cookie domain if If set, will be used for the session cookie domain if
``SESSION_COOKIE_DOMAIN`` is not set. Modern web browsers will not allow :data:`SESSION_COOKIE_DOMAIN` is not set. Modern web browsers will
setting cookies for domains without a dot. To use a domain locally, not allow setting cookies for domains without a dot. To use a domain
add any names that should route to the app to your ``hosts`` file. :: locally, add any names that should route to the app to your
``hosts`` file. ::
127.0.0.1 localhost.dev 127.0.0.1 localhost.dev
@ -318,10 +372,16 @@ The following configuration values are used internally by Flask:
``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING`` ``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING``
.. versionchanged:: 1.0 .. versionchanged:: 1.0
``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See ``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See
:ref:`logging` for information about configuration. :ref:`logging` for information about configuration.
Added :data:`ENV` to reflect the :envvar:`FLASK_ENV` environment
variable.
Added :data:`SESSION_COOKIE_SAMESITE` to control the session
cookie's ``SameSite`` option.
Configuring from Files Configuring from Files
---------------------- ----------------------
@ -588,4 +648,3 @@ Example usage for both::
# or via open_instance_resource: # or via open_instance_resource:
with app.open_instance_resource('application.cfg') as f: with app.open_instance_resource('application.cfg') as f:
config = f.read() config = f.read()

1
docs/contents.rst.inc

@ -56,7 +56,6 @@ Design notes, legal information and changelog are here for the interested.
unicode unicode
extensiondev extensiondev
styleguide styleguide
python3
upgrading upgrading
changelog changelog
license license

2
docs/deploying/fastcgi.rst

@ -111,7 +111,7 @@ Set yourapplication.fcgi::
#!/usr/bin/python #!/usr/bin/python
#: optional path to your local python site-packages folder #: optional path to your local python site-packages folder
import sys import sys
sys.path.insert(0, '<your_local_path>/lib/python2.6/site-packages') sys.path.insert(0, '<your_local_path>/lib/python<your_python_version>/site-packages')
from flup.server.fcgi import WSGIServer from flup.server.fcgi import WSGIServer
from yourapplication import app from yourapplication import app

5
docs/deploying/index.rst

@ -4,9 +4,8 @@ Deployment Options
================== ==================
While lightweight and easy to use, **Flask's built-in server is not suitable While lightweight and easy to use, **Flask's built-in server is not suitable
for production** as it doesn't scale well and by default serves only one for production** as it doesn't scale well. Some of the options available for
request at a time. Some of the options available for properly running Flask in properly running Flask in production are documented here.
production are documented here.
If you want to deploy your Flask application to a WSGI server not listed here, If you want to deploy your Flask application to a WSGI server not listed here,
look up the server documentation about how to use a WSGI app with it. Just look up the server documentation about how to use a WSGI app with it. Just

2
docs/deploying/wsgi-standalone.rst

@ -78,7 +78,7 @@ as well; see ``twistd -h`` and ``twistd web -h`` for more information. For
example, to run a Twisted Web server in the foreground, on port 8080, with an example, to run a Twisted Web server in the foreground, on port 8080, with an
application from ``myproject``:: application from ``myproject``::
twistd -n web --port 8080 --wsgi myproject.app twistd -n web --port tcp:8080 --wsgi myproject.app
.. _Twisted: https://twistedmatrix.com/ .. _Twisted: https://twistedmatrix.com/
.. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb .. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb

4
docs/errorhandling.rst

@ -42,9 +42,9 @@ aggregates duplicate errors, captures the full stack trace and local
variables for debugging, and sends you mails based on new errors or variables for debugging, and sends you mails based on new errors or
frequency thresholds. frequency thresholds.
To use Sentry you need to install the `raven` client:: To use Sentry you need to install the `raven` client with extra `flask` dependencies::
$ pip install raven $ pip install raven[flask]
And then add this to your Flask app:: And then add this to your Flask app::

82
docs/extensiondev.rst

@ -159,19 +159,10 @@ The Extension Code
Here's the contents of the `flask_sqlite3.py` for copy/paste:: Here's the contents of the `flask_sqlite3.py` for copy/paste::
import sqlite3 import sqlite3
from flask import current_app from flask import current_app, _app_ctx_stack
# Find the stack on which we want to store the database connection.
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
# before that we need to use the _request_ctx_stack.
try:
from flask import _app_ctx_stack as stack
except ImportError:
from flask import _request_ctx_stack as stack
class SQLite3(object): class SQLite3(object):
def __init__(self, app=None): def __init__(self, app=None):
self.app = app self.app = app
if app is not None: if app is not None:
@ -179,24 +170,19 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste::
def init_app(self, app): def init_app(self, app):
app.config.setdefault('SQLITE3_DATABASE', ':memory:') app.config.setdefault('SQLITE3_DATABASE', ':memory:')
# Use the newstyle teardown_appcontext if it's available,
# otherwise fall back to the request context
if hasattr(app, 'teardown_appcontext'):
app.teardown_appcontext(self.teardown) app.teardown_appcontext(self.teardown)
else:
app.teardown_request(self.teardown)
def connect(self): def connect(self):
return sqlite3.connect(current_app.config['SQLITE3_DATABASE']) return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])
def teardown(self, exception): def teardown(self, exception):
ctx = stack.top ctx = _app_ctx_stack.top
if hasattr(ctx, 'sqlite3_db'): if hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db.close() ctx.sqlite3_db.close()
@property @property
def connection(self): def connection(self):
ctx = stack.top ctx = _app_ctx_stack.top
if ctx is not None: if ctx is not None:
if not hasattr(ctx, 'sqlite3_db'): if not hasattr(ctx, 'sqlite3_db'):
ctx.sqlite3_db = self.connect() ctx.sqlite3_db = self.connect()
@ -212,9 +198,7 @@ So here's what these lines of code do:
factory pattern for creating applications. The ``init_app`` will set the factory pattern for creating applications. The ``init_app`` will set the
configuration for the database, defaulting to an in memory database if configuration for the database, defaulting to an in memory database if
no configuration is supplied. In addition, the ``init_app`` method attaches no configuration is supplied. In addition, the ``init_app`` method attaches
the ``teardown`` handler. It will try to use the newstyle app context the ``teardown`` handler.
handler and if it does not exist, falls back to the request context
one.
3. Next, we define a ``connect`` method that opens a database connection. 3. Next, we define a ``connect`` method that opens a database connection.
4. Finally, we add a ``connection`` property that on first access opens 4. Finally, we add a ``connection`` property that on first access opens
the database connection and stores it on the context. This is also the database connection and stores it on the context. This is also
@ -224,9 +208,7 @@ So here's what these lines of code do:
Note here that we're attaching our database connection to the top Note here that we're attaching our database connection to the top
application context via ``_app_ctx_stack.top``. Extensions should use application context via ``_app_ctx_stack.top``. Extensions should use
the top context for storing their own information with a sufficiently the top context for storing their own information with a sufficiently
complex name. Note that we're falling back to the complex name.
``_request_ctx_stack.top`` if the application is using an older
version of Flask that does not support it.
So why did we decide on a class-based approach here? Because using our So why did we decide on a class-based approach here? Because using our
extension looks something like this:: extension looks something like this::
@ -245,9 +227,8 @@ You can then use the database from views like this::
cur = db.connection.cursor() cur = db.connection.cursor()
cur.execute(...) cur.execute(...)
Likewise if you are outside of a request but you are using Flask 0.9 or Likewise if you are outside of a request you can use the database by
later with the app context support, you can use the database in the same pushing an app context::
way::
with app.app_context(): with app.app_context():
cur = db.connection.cursor() cur = db.connection.cursor()
@ -291,34 +272,6 @@ teardown of a request, the ``sqlite3_db`` connection is closed. By using
this pattern, the *same* connection to the sqlite3 database is accessible this pattern, the *same* connection to the sqlite3 database is accessible
to anything that needs it for the duration of the request. to anything that needs it for the duration of the request.
If the :data:`~flask._app_ctx_stack` does not exist because the user uses
an old version of Flask, it is recommended to fall back to
:data:`~flask._request_ctx_stack` which is bound to a request.
Teardown Behavior
-----------------
*This is only relevant if you want to support Flask 0.6 and older*
Due to the change in Flask 0.7 regarding functions that are run at the end
of the request your extension will have to be extra careful there if it
wants to continue to support older versions of Flask. The following
pattern is a good way to support both::
def close_connection(response):
ctx = _request_ctx_stack.top
ctx.sqlite3_db.close()
return response
if hasattr(app, 'teardown_request'):
app.teardown_request(close_connection)
else:
app.after_request(close_connection)
Strictly speaking the above code is wrong, because teardown functions are
passed the exception and typically don't return anything. However because
the return value is discarded this will just work assuming that the code
in between does not touch the passed parameter.
Learn from Others Learn from Others
----------------- -----------------
@ -383,26 +336,7 @@ extension to be approved you have to follow these guidelines:
(``PackageName==dev``). (``PackageName==dev``).
9. The ``zip_safe`` flag in the setup script must be set to ``False``, 9. The ``zip_safe`` flag in the setup script must be set to ``False``,
even if the extension would be safe for zipping. even if the extension would be safe for zipping.
10. An extension currently has to support Python 2.7, Python 3.3 and higher. 10. An extension currently has to support Python 3.4 and newer and 2.7.
Extension Import Transition
---------------------------
In early versions of Flask we recommended using namespace packages for Flask
extensions, of the form ``flaskext.foo``. This turned out to be problematic in
practice because it meant that multiple ``flaskext`` packages coexist.
Consequently we have recommended to name extensions ``flask_foo`` over
``flaskext.foo`` for a long time.
Flask 0.8 introduced a redirect import system as a compatibility aid for app
developers: Importing ``flask.ext.foo`` would try ``flask_foo`` and
``flaskext.foo`` in that order.
As of Flask 0.11, most Flask extensions have transitioned to the new naming
schema. The ``flask.ext.foo`` compatibility alias is still in Flask 0.11 but is
now deprecated -- you should use ``flask_foo``.
.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/ .. _OAuth extension: https://pythonhosted.org/Flask-OAuth/

71
docs/extensions.rst

@ -1,58 +1,53 @@
.. _extensions: .. _extensions:
Flask Extensions Extensions
================ ==========
Extensions are extra packages that add functionality to a Flask
application. For example, an extension might add support for sending
email or connecting to a database. Some extensions add entire new
frameworks to help build certain types of applications, like a ReST API.
Flask extensions extend the functionality of Flask in various different
ways. For instance they add support for databases and other common tasks.
Finding Extensions Finding Extensions
------------------ ------------------
Flask extensions are listed on the `Flask Extension Registry`_ and can be Flask extensions are usually named "Flask-Foo" or "Foo-Flask". Many
downloaded with :command:`easy_install` or :command:`pip`. If you add a Flask extension extensions are listed in the `Extension Registry`_, which can be updated
as dependency to your :file:`requirements.txt` or :file:`setup.py` file they are by extension developers. You can also search PyPI for packages tagged
usually installed with a simple command or when your application installs. with `Framework :: Flask <pypi_>`_.
Using Extensions Using Extensions
---------------- ----------------
Extensions typically have documentation that goes along that shows how to Consult each extension's documentation for installation, configuration,
use it. There are no general rules in how extensions are supposed to and usage instructions. Generally, extensions pull their own
behave but they are imported from common locations. If you have an configuration from :attr:`app.config <flask.Flask.config>` and are
extension called ``Flask-Foo`` or ``Foo-Flask`` it should be always passed an application instance during initialization. For example,
importable from ``flask_foo``:: an extension caled "Flask-Foo" might be used like this::
import flask_foo from flask_foo import Foo
Building Extensions foo = Foo()
-------------------
While `Flask Extension Registry`_ contains many Flask extensions, you may not find app = Flask(__name__)
an extension that fits your need. If this is the case, you can always create your own. app.config.update(
Consider reading :ref:`extension-dev` to develop your own Flask extension. FOO_BAR='baz',
FOO_SPAM='eggs',
)
Flask Before 0.8 foo.init_app(app)
----------------
If you are using Flask 0.7 or earlier the :data:`flask.ext` package will not
exist, instead you have to import from ``flaskext.foo`` or ``flask_foo``
depending on how the extension is distributed. If you want to develop an
application that supports Flask 0.7 or earlier you should still import
from the :data:`flask.ext` package. We provide you with a compatibility
module that provides this package for older versions of Flask. You can
download it from GitHub: `flaskext_compat.py`_
And here is how you can use it:: Building Extensions
-------------------
import flaskext_compat
flaskext_compat.activate()
from flask.ext import foo
Once the ``flaskext_compat`` module is activated the :data:`flask.ext` will While the `Extension Registry`_ contains many Flask extensions, you may
exist and you can start importing from there. not find an extension that fits your need. If this is the case, you can
create your own. Read :ref:`extension-dev` to develop your own Flask
extension.
.. _Flask Extension Registry: http://flask.pocoo.org/extensions/ .. _Extension Registry: http://flask.pocoo.org/extensions/
.. _flaskext_compat.py: https://raw.githubusercontent.com/pallets/flask/master/scripts/flaskext_compat.py .. _pypi: https://pypi.python.org/pypi?:action=browse&c=585

29
docs/index.rst

@ -7,25 +7,20 @@ Welcome to Flask
:alt: Flask: web development, one drop at a time :alt: Flask: web development, one drop at a time
:class: floatingflask :class: floatingflask
Welcome to Flask's documentation. This documentation is divided into Welcome to Flask's documentation. Get started with :ref:`installation`
different parts. I recommend that you get started with and then get an overview with the :ref:`quickstart`. There is also a
:ref:`installation` and then head over to the :ref:`quickstart`. more detailed :ref:`tutorial` that shows how to create a small but
Besides the quickstart, there is also a more detailed :ref:`tutorial` that complete application with Flask. Common patterns are described in the
shows how to create a complete (albeit small) application with Flask. If :ref:`patterns` section. The rest of the docs desribe each component of
you'd rather dive into the internals of Flask, check out Flask in detail, with a full reference in the :ref:`api` section.
the :ref:`api` documentation. Common patterns are described in the
:ref:`patterns` section.
Flask depends on two external libraries: the `Jinja2`_ template Flask depends on the `Jinja`_ template engine and the `Werkzeug`_ WSGI
engine and the `Werkzeug`_ WSGI toolkit. These libraries are not documented toolkit. The documentation for these libraries can be found at:
here. If you want to dive into their documentation, check out the
following links:
- `Jinja2 Documentation <http://jinja.pocoo.org/docs>`_ - `Jinja documentation <http://jinja.pocoo.org/docs>`_
- `Werkzeug Documentation <http://werkzeug.pocoo.org/docs>`_ - `Werkzeug documentation <http://werkzeug.pocoo.org/docs>`_
.. _Jinja: https://www.palletsprojects.com/p/jinja/
.. _Jinja2: http://jinja.pocoo.org/ .. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/
.. _Werkzeug: http://werkzeug.pocoo.org/
.. include:: contents.rst.inc .. include:: contents.rst.inc

4
docs/installation.rst

@ -6,8 +6,8 @@ Installation
Python Version Python Version
-------------- --------------
We recommend using the latest version of Python 3. Flask supports Python 3.3 We recommend using the latest version of Python 3. Flask supports Python 3.4
and newer, Python 2.6 and newer, and PyPy. and newer, Python 2.7, and PyPy.
Dependencies Dependencies
------------ ------------

16
docs/patterns/appfactories.rst

@ -89,18 +89,20 @@ For more information about the design of extensions refer to :doc:`/extensiondev
Using Applications Using Applications
------------------ ------------------
To use such an application you have to create it in a separate file first, To run such an application, you can use the :command:`flask` command::
otherwise the :command:`flask` command won't be able to find it. Here's an
example :file:`exampleapp.py` file that creates such an application::
from yourapplication import create_app export FLASK_APP=myapp
app = create_app('/path/to/config.cfg') flask run
It can then be used with the :command:`flask` command:: Flask will automatically detect the factory (``create_app`` or ``make_app``)
in ``myapp``. You can also pass arguments to the factory like this::
export FLASK_APP=exampleapp export FLASK_APP="myapp:create_app('dev')"
flask run flask run
Then the ``create_app`` factory in ``myapp`` is called with the string
``'dev'`` as the argument. See :doc:`/cli` for more detail.
Factory Improvements Factory Improvements
-------------------- --------------------

2
docs/patterns/fileuploads.rst

@ -65,7 +65,7 @@ the file and redirects the user to the URL for the uploaded file::
if file and allowed_file(file.filename): if file and allowed_file(file.filename):
filename = secure_filename(file.filename) filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', return redirect(url_for('upload_file',
filename=filename)) filename=filename))
return ''' return '''
<!doctype html> <!doctype html>

6
docs/patterns/packages.rst

@ -65,10 +65,10 @@ that tells Flask where to find the application instance::
export FLASK_APP=yourapplication export FLASK_APP=yourapplication
If you are outside of the project directory make sure to provide the exact If you are outside of the project directory make sure to provide the exact
path to your application directory. Similarly you can turn on "debug path to your application directory. Similarly you can turn on the
mode" with this environment variable:: development features like this::
export FLASK_DEBUG=true export FLASK_ENV=development
In order to install and run the application you need to issue the following In order to install and run the application you need to issue the following
commands:: commands::

2
docs/patterns/sqlite3.rst

@ -67,7 +67,7 @@ the application context by hand::
Easy Querying Easy Querying
------------- -------------
Now in each request handling function you can access `g.db` to get the Now in each request handling function you can access `get_db()` to get the
current open database connection. To simplify working with SQLite, a current open database connection. To simplify working with SQLite, a
row factory function is useful. It is executed for every result returned row factory function is useful. It is executed for every result returned
from the database to convert the result. For instance, in order to get from the database to convert the result. For instance, in order to get

23
docs/python3.rst

@ -1,23 +0,0 @@
.. _python3-support:
Python 3 Support
================
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.
You need to use Python 3.3 or higher. 3.2 and older are *not* supported.
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.
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.
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.

41
docs/quickstart.rst

@ -130,13 +130,14 @@ That is not very nice and Flask can do better. If you enable debug
support the server will reload itself on code changes, and it will also support the server will reload itself on code changes, and it will also
provide you with a helpful debugger if things go wrong. provide you with a helpful debugger if things go wrong.
To enable debug mode you can export the ``FLASK_DEBUG`` environment variable To enable all development features (including debug mode) you can export
the ``FLASK_ENV`` environment variable and set it to ``development``
before running the server:: before running the server::
$ export FLASK_DEBUG=1 $ export FLASK_ENV=development
$ flask run $ flask run
(On Windows you need to use ``set`` instead of ``export``). (On Windows you need to use ``set`` instead of ``export``.)
This does the following things: This does the following things:
@ -144,6 +145,9 @@ This does the following things:
2. it activates the automatic reloader 2. it activates the automatic reloader
3. it enables the debug mode on the Flask application. 3. it enables the debug mode on the Flask application.
You can also control debug mode separately from the environment by
exporting ``FLASK_DEBUG=1``.
There are more parameters that are explained in the :ref:`server` docs. There are more parameters that are explained in the :ref:`server` docs.
.. admonition:: Attention .. admonition:: Attention
@ -224,7 +228,7 @@ Converter types:
Unique URLs / Redirection Behavior Unique URLs / Redirection Behavior
`````````````````````````````````` ``````````````````````````````````
Take these two rules:: The following two rules differ in their use of a trailing slash. ::
@app.route('/projects/') @app.route('/projects/')
def projects(): def projects():
@ -234,20 +238,17 @@ Take these two rules::
def about(): def about():
return 'The about page' return 'The about page'
Though they look similar, they differ in their use of the trailing slash in The canonical URL for the ``projects`` endpoint has a trailing slash.
the URL. In the first case, the canonical URL for the ``projects`` endpoint It's similar to a folder in a file system. If you access the URL without
uses a trailing slash. It's similar to a folder in a file system; if you a trailing slash, Flask redirects you to the canonical URL with the
access the URL without a trailing slash, Flask redirects you to the trailing slash.
canonical URL with the trailing slash.
In the second case, however, the URL definition lacks a trailing slash, The canonical URL for the ``about`` endpoint does not have a trailing
like the pathname of a file on UNIX-like systems. Accessing the URL with a slash. It's similar to the pathname of a file. Accessing the URL with a
trailing slash produces a 404 “Not Found” error. trailing slash produces a 404 "Not Found" error. This helps keep URLs
unique for these resources, which helps search engines avoid indexing
the same page twice.
This behavior allows relative URLs to continue working even if the trailing
slash is omitted, consistent with how Apache and other servers work. Also,
the URLs will stay unique, which helps search engines avoid indexing the
same page twice.
.. _url-building: .. _url-building:
@ -267,7 +268,9 @@ Why would you want to build URLs using the URL reversing function
manually change hard-coded URLs. manually change hard-coded URLs.
3. URL building handles escaping of special characters and Unicode data 3. URL building handles escaping of special characters and Unicode data
transparently. transparently.
4. If your application is placed outside the URL root, for example, in 4. The generated paths are always absolute, avoiding unexpected behavior
of relative paths in browsers.
5. If your application is placed outside the URL root, for example, in
``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
handles that for you. handles that for you.
@ -315,9 +318,9 @@ of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if request.method == 'POST': if request.method == 'POST':
do_the_login() return do_the_login()
else: else:
show_the_login_form() return show_the_login_form()
If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise, and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,

16
docs/security.rst

@ -184,6 +184,9 @@ contains the same data. ::
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
.. _security-cookie:
Set-Cookie options Set-Cookie options
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -194,17 +197,21 @@ They can be set on other cookies too.
- ``Secure`` limits cookies to HTTPS traffic only. - ``Secure`` limits cookies to HTTPS traffic only.
- ``HttpOnly`` protects the contents of cookies from being read with - ``HttpOnly`` protects the contents of cookies from being read with
JavaScript. JavaScript.
- ``SameSite`` ensures that cookies can only be requested from the same - ``SameSite`` restricts how cookies are sent with requests from
domain that created them. It is not supported by Flask yet. external sites. Can be set to ``'Lax'`` (recommended) or ``'Strict'``.
``Lax`` prevents sending cookies with CSRF-prone requests from
external sites, such as submitting a form. ``Strict`` prevents sending
cookies with all external requests, including following regular links.
:: ::
app.config.update( app.config.update(
SESSION_COOKIE_SECURE=True, SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
) )
response.set_cookie('username', 'flask', secure=True, httponly=True) response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax')
Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after
the given time, or the current time plus the age, respectively. If neither the given time, or the current time plus the age, respectively. If neither
@ -237,6 +244,9 @@ values (or any values that need secure signatures).
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies - https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
.. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute
HTTP Public Key Pinning (HPKP) HTTP Public Key Pinning (HPKP)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

20
docs/server.rst

@ -12,15 +12,17 @@ but you can also continue using the :meth:`Flask.run` method.
Command Line Command Line
------------ ------------
The :command:`flask` command line script (:ref:`cli`) is strongly recommended for The :command:`flask` command line script (:ref:`cli`) is strongly
development because it provides a superior reload experience due to how it recommended for development because it provides a superior reload
loads the application. The basic usage is like this:: experience due to how it loads the application. The basic usage is like
this::
$ export FLASK_APP=my_application $ export FLASK_APP=my_application
$ export FLASK_DEBUG=1 $ export FLASK_ENV=development
$ flask run $ flask run
This will enable the debugger, the reloader and then start the server on This enables the development environment, including the interactive
debugger and reloader, and then starts the server on
*http://localhost:5000/*. *http://localhost:5000/*.
The individual features of the server can be controlled by passing more The individual features of the server can be controlled by passing more
@ -29,6 +31,14 @@ disabled::
$ flask run --no-reload $ flask run --no-reload
.. note::
Prior to Flask 1.0 the :envvar:`FLASK_ENV` environment variable was
not supported and you needed to enable debug mode by exporting
``FLASK_DEBUG=1``. This can still be used to control debug mode, but
you should prefer setting the development environment as shown
above.
In Code In Code
------- -------

57
docs/testing.rst

@ -406,3 +406,60 @@ Passing the ``json`` argument in the test client methods sets the request data
to the JSON-serialized object and sets the content type to to the JSON-serialized object and sets the content type to
``application/json``. You can get the JSON data from the request or response ``application/json``. You can get the JSON data from the request or response
with ``get_json``. with ``get_json``.
.. _testing-cli:
Testing CLI Commands
--------------------
Click comes with `utilities for testing`_ your CLI commands. A
:class:`~click.testing.CliRunner` runs commands in isolation and
captures the output in a :class:`~click.testing.Result` object.
Flask provides :meth:`~flask.Flask.test_cli_runner` to create a
:class:`~flask.testing.FlaskCliRunner` that passes the Flask app to the
CLI automatically. Use its :meth:`~flask.testing.FlaskCliRunner.invoke`
method to call commands in the same way they would be called from the
command line. ::
import click
@app.cli.command('hello')
@click.option('--name', default='World')
def hello_command(name)
click.echo(f'Hello, {name}!')
def test_hello():
runner = app.test_cli_runner()
# invoke the command directly
result = runner.invoke(hello_command, ['--name', 'Flask'])
assert 'Hello, Flask' in result.output
# or by name
result = runner.invoke(args=['hello'])
assert 'World' in result.output
In the example above, invoking the command by name is useful because it
verifies that the command was correctly registered with the app.
If you want to test how your command parses parameters, without running
the command, use its :meth:`~click.BaseCommand.make_context` method.
This is useful for testing complex validation rules and custom types. ::
def upper(ctx, param, value):
if value is not None:
return value.upper()
@app.cli.command('hello')
@click.option('--name', default='World', callback=upper)
def hello_command(name)
click.echo(f'Hello, {name}!')
def test_hello_params():
context = hello_command.make_context('hello', ['--name', 'flask'])
assert context.params['name'] == 'FLASK'
.. _click: http://click.pocoo.org/
.. _utilities for testing: http://click.pocoo.org/testing

6
docs/tutorial/packaging.rst

@ -78,11 +78,13 @@ With that out of the way, you should be able to start up the application.
Do this on Mac or Linux with the following commands in ``flaskr/``:: Do this on Mac or Linux with the following commands in ``flaskr/``::
export FLASK_APP=flaskr export FLASK_APP=flaskr
export FLASK_DEBUG=true export FLASK_ENV=development
flask run flask run
(In case you are on Windows you need to use ``set`` instead of ``export``). (In case you are on Windows you need to use ``set`` instead of ``export``).
The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger. Exporting ``FLASK_ENV=development`` turns on all development features
such as enabling the interactive debugger.
*Never leave debug mode activated in a production system*, because it will *Never leave debug mode activated in a production system*, because it will
allow users to execute code on the server! allow users to execute code on the server!

9
examples/blueprintexample/blueprintexample.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
Blueprint Example
~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from flask import Flask from flask import Flask
from simple_page.simple_page import simple_page from simple_page.simple_page import simple_page

6
examples/blueprintexample/test_blueprintexample.py

@ -1,10 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Blueprint Example Tests Blueprint Example Tests
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
Tests the Blueprint example app :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
""" """
import pytest import pytest
import blueprintexample import blueprintexample

2
examples/flaskr/flaskr/blueprints/flaskr.py

@ -6,7 +6,7 @@
A microblog example application written as Flask tutorial with A microblog example application written as Flask tutorial with
Flask and sqlite3. Flask and sqlite3.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

2
examples/flaskr/flaskr/factory.py

@ -6,7 +6,7 @@
A microblog example application written as Flask tutorial with A microblog example application written as Flask tutorial with
Flask and sqlite3. Flask and sqlite3.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

2
examples/flaskr/setup.py

@ -5,7 +5,7 @@
Tests the Flaskr application. Tests the Flaskr application.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

26
examples/flaskr/tests/test_flaskr.py

@ -5,7 +5,7 @@
Tests the Flaskr application. Tests the Flaskr application.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -17,33 +17,25 @@ from flaskr.blueprints.flaskr import init_db
@pytest.fixture @pytest.fixture
def app(request): def app():
db_fd, db_path = tempfile.mkstemp()
db_fd, temp_db_location = tempfile.mkstemp()
config = { config = {
'DATABASE': temp_db_location, 'DATABASE': db_path,
'TESTING': True, 'TESTING': True,
'DB_FD': db_fd
} }
app = create_app(config=config) app = create_app(config=config)
with app.app_context(): with app.app_context():
init_db() init_db()
yield app yield app
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(request, app):
client = app.test_client()
def teardown():
os.close(app.config['DB_FD'])
os.unlink(app.config['DATABASE'])
request.addfinalizer(teardown)
return client @pytest.fixture
def client(app):
return app.test_client()
def login(client, username, password): def login(client, username, password):

3
examples/jqueryexample/jqueryexample.py

@ -5,9 +5,10 @@
A simple application that shows how Flask and jQuery get along. A simple application that shows how Flask and jQuery get along.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from flask import Flask, jsonify, render_template, request from flask import Flask, jsonify, render_template, request
app = Flask(__name__) app = Flask(__name__)

2
examples/minitwit/minitwit/minitwit.py

@ -5,7 +5,7 @@
A microblogging application written with Flask and sqlite3. A microblogging application written with Flask and sqlite3.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

3
examples/minitwit/tests/test_minitwit.py

@ -5,9 +5,10 @@
Tests the MiniTwit application. Tests the MiniTwit application.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import os import os
import tempfile import tempfile
import pytest import pytest

9
examples/patterns/largerapp/tests/test_largerapp.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
Larger App Tests
~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from yourapplication import app from yourapplication import app
import pytest import pytest

9
examples/patterns/largerapp/yourapplication/__init__.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
yourapplication
~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from flask import Flask from flask import Flask
app = Flask('yourapplication') app = Flask('yourapplication')

9
examples/patterns/largerapp/yourapplication/views.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
yourapplication.views
~~~~~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from yourapplication import app from yourapplication import app
@app.route('/') @app.route('/')

2
flask/__init__.py

@ -6,7 +6,7 @@
A microframework based on Werkzeug. It's extensively documented A microframework based on Werkzeug. It's extensively documented
and follows best practice patterns. and follows best practice patterns.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

3
flask/__main__.py

@ -5,11 +5,10 @@
Alias for flask.run for the command line. Alias for flask.run for the command line.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
if __name__ == '__main__': if __name__ == '__main__':
from .cli import main from .cli import main
main(as_module=True) main(as_module=True)

3
flask/_compat.py

@ -7,9 +7,10 @@
version of six so we don't have to depend on a specific version version of six so we don't have to depend on a specific version
of it. of it.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import sys import sys
PY2 = sys.version_info[0] == 2 PY2 = sys.version_info[0] == 2

170
flask/app.py

@ -5,9 +5,10 @@
This module implements the central WSGI application object. This module implements the central WSGI application object.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import os import os
import sys import sys
import warnings import warnings
@ -27,7 +28,7 @@ from .config import Config, ConfigAttribute
from .ctx import AppContext, RequestContext, _AppCtxGlobals from .ctx import AppContext, RequestContext, _AppCtxGlobals
from .globals import _request_ctx_stack, g, request, session from .globals import _request_ctx_stack, g, request, session
from .helpers import _PackageBoundObject, \ from .helpers import _PackageBoundObject, \
_endpoint_from_view_func, find_package, get_debug_flag, \ _endpoint_from_view_func, find_package, get_env, get_debug_flag, \
get_flashed_messages, locked_cached_property, url_for get_flashed_messages, locked_cached_property, url_for
from .logging import create_logger from .logging import create_logger
from .sessions import SecureCookieSessionInterface from .sessions import SecureCookieSessionInterface
@ -122,8 +123,13 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.11 .. versionadded:: 0.11
The `root_path` parameter was added. The `root_path` parameter was added.
.. versionadded:: 0.13 .. versionadded:: 1.0
The `host_matching` and `static_host` parameters were added. The ``host_matching`` and ``static_host`` parameters were added.
.. versionadded:: 1.0
The ``subdomain_matching`` parameter was added. Subdomain
matching needs to be enabled manually now. Setting
:data:`SERVER_NAME` does not implicitly enable it.
:param import_name: the name of the application package :param import_name: the name of the application package
:param static_url_path: can be used to specify a different path for the :param static_url_path: can be used to specify a different path for the
@ -132,11 +138,13 @@ class Flask(_PackageBoundObject):
:param static_folder: the folder with static files that should be served :param static_folder: the folder with static files that should be served
at `static_url_path`. Defaults to the ``'static'`` at `static_url_path`. Defaults to the ``'static'``
folder in the root path of the application. folder in the root path of the application.
:param host_matching: sets the app's ``url_map.host_matching`` to the given :param static_host: the host to use when adding the static route.
value. Defaults to False. Defaults to None. Required when using ``host_matching=True``
:param static_host: the host to use when adding the static route. Defaults
to None. Required when using ``host_matching=True``
with a ``static_folder`` configured. with a ``static_folder`` configured.
:param host_matching: set ``url_map.host_matching`` attribute.
Defaults to False.
:param subdomain_matching: consider the subdomain relative to
:data:`SERVER_NAME` when matching routes. Defaults to False.
:param template_folder: the folder that contains the templates that should :param template_folder: the folder that contains the templates that should
be used by the application. Defaults to be used by the application. Defaults to
``'templates'`` folder in the root path of the ``'templates'`` folder in the root path of the
@ -196,15 +204,6 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.11 #: .. versionadded:: 0.11
config_class = Config config_class = Config
#: The debug flag. Set this to ``True`` to enable debugging of the
#: application. In debug mode the debugger will kick in when an unhandled
#: exception occurs and the integrated server will automatically reload
#: the application if changes in the code are detected.
#:
#: This attribute can also be configured from the config with the ``DEBUG``
#: configuration key. Defaults to ``False``.
debug = ConfigAttribute('DEBUG')
#: The testing flag. Set this to ``True`` to enable the test mode of #: The testing flag. Set this to ``True`` to enable the test mode of
#: Flask extensions (and in the future probably also Flask itself). #: Flask extensions (and in the future probably also Flask itself).
#: For example this might activate test helpers that have an #: For example this might activate test helpers that have an
@ -278,7 +277,8 @@ class Flask(_PackageBoundObject):
#: Default configuration parameters. #: Default configuration parameters.
default_config = ImmutableDict({ default_config = ImmutableDict({
'DEBUG': get_debug_flag(default=False), 'ENV': None,
'DEBUG': None,
'TESTING': False, 'TESTING': False,
'PROPAGATE_EXCEPTIONS': None, 'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None,
@ -292,6 +292,7 @@ class Flask(_PackageBoundObject):
'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SECURE': False,
'SESSION_COOKIE_SAMESITE': None,
'SESSION_REFRESH_EACH_REQUEST': True, 'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None, 'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
@ -317,6 +318,14 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.7 #: .. versionadded:: 0.7
test_client_class = None test_client_class = None
#: The :class:`~click.testing.CliRunner` subclass, by default
#: :class:`~flask.testing.FlaskCliRunner` that is used by
#: :meth:`test_cli_runner`. Its ``__init__`` method should take a
#: Flask app object as the first argument.
#:
#: .. versionadded:: 1.0
test_cli_runner_class = None
#: the session interface to use. By default an instance of #: the session interface to use. By default an instance of
#: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
#: #:
@ -345,6 +354,7 @@ class Flask(_PackageBoundObject):
static_folder='static', static_folder='static',
static_host=None, static_host=None,
host_matching=False, host_matching=False,
subdomain_matching=False,
template_folder='templates', template_folder='templates',
instance_path=None, instance_path=None,
instance_relative_config=False, instance_relative_config=False,
@ -528,6 +538,7 @@ class Flask(_PackageBoundObject):
self.url_map = Map() self.url_map = Map()
self.url_map.host_matching = host_matching self.url_map.host_matching = host_matching
self.subdomain_matching = subdomain_matching
# tracks internally if the application already handled at least one # tracks internally if the application already handled at least one
# request. # request.
@ -647,7 +658,10 @@ class Flask(_PackageBoundObject):
root_path = self.root_path root_path = self.root_path
if instance_relative: if instance_relative:
root_path = self.instance_path root_path = self.instance_path
return self.config_class(root_path, self.default_config) defaults = dict(self.default_config)
defaults['ENV'] = get_env()
defaults['DEBUG'] = get_debug_flag()
return self.config_class(root_path, defaults)
def auto_find_instance_path(self): def auto_find_instance_path(self):
"""Tries to locate the instance path if it was not provided to the """Tries to locate the instance path if it was not provided to the
@ -790,25 +804,41 @@ class Flask(_PackageBoundObject):
rv.update(processor()) rv.update(processor())
return rv return rv
def _reconfigure_for_run_debug(self, debug): #: What environment the app is running in. Flask and extensions may
"""The ``run`` commands will set the application's debug flag. Some #: enable behaviors based on the environment, such as enabling debug
application configuration may already be calculated based on the #: mode. This maps to the :data:`ENV` config key. This is set by the
previous debug value. This method will recalculate affected values. #: :envvar:`FLASK_ENV` environment variable and may not behave as
#: expected if set in code.
Called by the :func:`flask.cli.run` command or :meth:`Flask.run` #:
method if the debug flag is set explicitly in the call. #: **Do not enable development when deploying in production.**
#:
#: Default: ``'production'``
env = ConfigAttribute('ENV')
:param debug: the new value of the debug flag def _get_debug(self):
return self.config['DEBUG']
.. versionadded:: 1.0 def _set_debug(self, value):
Reconfigures ``app.jinja_env.auto_reload``. self.config['DEBUG'] = value
"""
self.debug = debug
self.jinja_env.auto_reload = self.templates_auto_reload self.jinja_env.auto_reload = self.templates_auto_reload
def run( #: Whether debug mode is enabled. When using ``flask run`` to start
self, host=None, port=None, debug=None, load_dotenv=True, **options #: the development server, an interactive debugger will be shown for
): #: unhandled exceptions, and the server will be reloaded when code
#: changes. This maps to the :data:`DEBUG` config key. This is
#: enabled when :attr:`env` is ``'development'`` and is overridden
#: by the ``FLASK_DEBUG`` environment variable. It may not behave as
#: expected if set in code.
#:
#: **Do not enable debug mode when deploying in production.**
#:
#: Default: ``True`` if :attr:`env` is ``'development'``, or
#: ``False`` otherwise.
debug = property(_get_debug, _set_debug)
del _get_debug, _set_debug
def run(self, host=None, port=None, debug=None,
load_dotenv=True, **options):
"""Runs the application on a local development server. """Runs the application on a local development server.
Do not use ``run()`` in a production setting. It is not intended to Do not use ``run()`` in a production setting. It is not intended to
@ -856,9 +886,15 @@ class Flask(_PackageBoundObject):
If installed, python-dotenv will be used to load environment If installed, python-dotenv will be used to load environment
variables from :file:`.env` and :file:`.flaskenv` files. variables from :file:`.env` and :file:`.flaskenv` files.
.. versionchanged:: 0.10 If set, the :envvar:`FLASK_ENV` and :envvar:`FLASK_DEBUG`
The default port is now picked from the ``SERVER_NAME`` variable. environment variables will override :attr:`env` and
:attr:`debug`.
Threaded mode is enabled by default.
.. versionchanged:: 0.10
The default port is now picked from the ``SERVER_NAME``
variable.
""" """
# Change this into a no-op if the server is invoked from the # Change this into a no-op if the server is invoked from the
# command line. Have a look at cli.py for more information. # command line. Have a look at cli.py for more information.
@ -868,15 +904,22 @@ class Flask(_PackageBoundObject):
return return
if load_dotenv: if load_dotenv:
from flask.cli import load_dotenv cli.load_dotenv()
load_dotenv()
# if set, let env vars override previous values
if 'FLASK_ENV' in os.environ:
self.env = get_env()
self.debug = get_debug_flag()
elif 'FLASK_DEBUG' in os.environ:
self.debug = get_debug_flag()
# debug passed to method overrides all other sources
if debug is not None: if debug is not None:
self._reconfigure_for_run_debug(bool(debug)) self.debug = bool(debug)
_host = '127.0.0.1' _host = '127.0.0.1'
_port = 5000 _port = 5000
server_name = self.config.get("SERVER_NAME") server_name = self.config.get('SERVER_NAME')
sn_host, sn_port = None, None sn_host, sn_port = None, None
if server_name: if server_name:
@ -884,8 +927,12 @@ class Flask(_PackageBoundObject):
host = host or sn_host or _host host = host or sn_host or _host
port = int(port or sn_port or _port) port = int(port or sn_port or _port)
options.setdefault('use_reloader', self.debug) options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug) options.setdefault('use_debugger', self.debug)
options.setdefault('threaded', True)
cli.show_server_banner(self.env, self.debug, self.name)
from werkzeug.serving import run_simple from werkzeug.serving import run_simple
@ -953,6 +1000,23 @@ class Flask(_PackageBoundObject):
from flask.testing import FlaskClient as cls from flask.testing import FlaskClient as cls
return cls(self, self.response_class, use_cookies=use_cookies, **kwargs) return cls(self, self.response_class, use_cookies=use_cookies, **kwargs)
def test_cli_runner(self, **kwargs):
"""Create a CLI runner for testing CLI commands.
See :ref:`testing-cli`.
Returns an instance of :attr:`test_cli_runner_class`, by default
:class:`~flask.testing.FlaskCliRunner`. The Flask app object is
passed as the first argument.
.. versionadded:: 1.0
"""
cls = self.test_cli_runner_class
if cls is None:
from flask.testing import FlaskCliRunner as cls
return cls(self, **kwargs)
def open_session(self, request): def open_session(self, request):
"""Creates or opens a new session. Default implementation stores all """Creates or opens a new session. Default implementation stores all
session data in a signed cookie. This requires that the session data in a signed cookie. This requires that the
@ -1055,7 +1119,8 @@ class Flask(_PackageBoundObject):
return iter(self._blueprint_order) return iter(self._blueprint_order)
@setupmethod @setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): def add_url_rule(self, rule, endpoint=None, view_func=None,
provide_automatic_options=None, **options):
"""Connects a URL rule. Works exactly like the :meth:`route` """Connects a URL rule. Works exactly like the :meth:`route`
decorator. If a view_func is provided it will be registered with the decorator. If a view_func is provided it will be registered with the
endpoint. endpoint.
@ -1922,18 +1987,29 @@ class Flask(_PackageBoundObject):
def create_url_adapter(self, request): def create_url_adapter(self, request):
"""Creates a URL adapter for the given request. The URL adapter """Creates a URL adapter for the given request. The URL adapter
is created at a point where the request context is not yet set up is created at a point where the request context is not yet set
so the request is passed explicitly. up so the request is passed explicitly.
.. versionadded:: 0.6 .. versionadded:: 0.6
.. versionchanged:: 0.9 .. versionchanged:: 0.9
This can now also be called without a request object when the This can now also be called without a request object when the
URL adapter is created for the application context. URL adapter is created for the application context.
.. versionchanged:: 1.0
:data:`SERVER_NAME` no longer implicitly enables subdomain
matching. Use :attr:`subdomain_matching` instead.
""" """
if request is not None: if request is not None:
return self.url_map.bind_to_environ(request.environ, # If subdomain matching is disabled (the default), use the
server_name=self.config['SERVER_NAME']) # default subdomain in all cases. This should be the default
# in Werkzeug but it currently does not have that feature.
subdomain = ((self.url_map.default_subdomain or None)
if not self.subdomain_matching else None)
return self.url_map.bind_to_environ(
request.environ,
server_name=self.config['SERVER_NAME'],
subdomain=subdomain)
# We need at the very least the server name to be set for this # We need at the very least the server name to be set for this
# to work. # to work.
if self.config['SERVER_NAME'] is not None: if self.config['SERVER_NAME'] is not None:
@ -2109,8 +2185,8 @@ class Flask(_PackageBoundObject):
return RequestContext(self, environ) return RequestContext(self, environ)
def test_request_context(self, *args, **kwargs): def test_request_context(self, *args, **kwargs):
"""Creates a WSGI environment from the given values (see """Creates a :class:`~flask.ctx.RequestContext` from the given values
:class:`werkzeug.test.EnvironBuilder` for more information, this (see :class:`werkzeug.test.EnvironBuilder` for more information, this
function accepts the same arguments plus two additional). function accepts the same arguments plus two additional).
Additional arguments (only if ``base_url`` is not specified): Additional arguments (only if ``base_url`` is not specified):

5
flask/blueprints.py

@ -6,7 +6,7 @@
Blueprints are the recommended way to implement larger or more Blueprints are the recommended way to implement larger or more
pluggable applications in Flask 0.7 and later. pluggable applications in Flask 0.7 and later.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from functools import update_wrapper from functools import update_wrapper
@ -52,6 +52,9 @@ class BlueprintSetupState(object):
#: The prefix that should be used for all URLs defined on the #: The prefix that should be used for all URLs defined on the
#: blueprint. #: blueprint.
if url_prefix and url_prefix[-1] == '/':
url_prefix = url_prefix[:-1]
self.url_prefix = url_prefix self.url_prefix = url_prefix
#: A dictionary with URL defaults that is added to each and every #: A dictionary with URL defaults that is added to each and every

214
flask/cli.py

@ -5,15 +5,17 @@
A simple command line application to run flask apps. A simple command line application to run flask apps.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from __future__ import print_function from __future__ import print_function
import ast import ast
import inspect import inspect
import os import os
import re import re
import ssl
import sys import sys
import traceback import traceback
from functools import update_wrapper from functools import update_wrapper
@ -21,11 +23,12 @@ from operator import attrgetter
from threading import Lock, Thread from threading import Lock, Thread
import click import click
from werkzeug.utils import import_string
from . import __version__ from . import __version__
from ._compat import getargspec, iteritems, reraise from ._compat import getargspec, iteritems, reraise, text_type
from .globals import current_app from .globals import current_app
from .helpers import get_debug_flag from .helpers import get_debug_flag, get_env
try: try:
import dotenv import dotenv
@ -75,6 +78,8 @@ def find_best_app(script_info, module):
if isinstance(app, Flask): if isinstance(app, Flask):
return app return app
except TypeError: except TypeError:
if not _called_with_wrong_args(app_factory):
raise
raise NoAppException( raise NoAppException(
'Detected factory "{factory}" in module "{module}", but ' 'Detected factory "{factory}" in module "{module}", but '
'could not call it without arguments. Use ' 'could not call it without arguments. Use '
@ -111,6 +116,30 @@ def call_factory(script_info, app_factory, arguments=()):
return app_factory() return app_factory()
def _called_with_wrong_args(factory):
"""Check whether calling a function raised a ``TypeError`` because
the call failed or because something in the factory raised the
error.
:param factory: the factory function that was called
:return: true if the call failed
"""
tb = sys.exc_info()[2]
try:
while tb is not None:
if tb.tb_frame.f_code is factory.__code__:
# in the factory, it was called successfully
return False
tb = tb.tb_next
# didn't reach the factory
return True
finally:
del tb
def find_app_by_string(script_info, module, app_name): def find_app_by_string(script_info, module, app_name):
"""Checks if the given string is a variable name or a function. If it is a """Checks if the given string is a variable name or a function. If it is a
function, it checks for specified arguments and whether it takes a function, it checks for specified arguments and whether it takes a
@ -148,6 +177,9 @@ def find_app_by_string(script_info, module, app_name):
try: try:
app = call_factory(script_info, attr, args) app = call_factory(script_info, attr, args)
except TypeError as e: except TypeError as e:
if not _called_with_wrong_args(attr):
raise
raise NoAppException( raise NoAppException(
'{e}\nThe factory "{app_name}" in module "{module}" could not ' '{e}\nThe factory "{app_name}" in module "{module}" could not '
'be called with the specified arguments.'.format( 'be called with the specified arguments.'.format(
@ -341,9 +373,8 @@ class ScriptInfo(object):
else: else:
for path in ('wsgi.py', 'app.py'): for path in ('wsgi.py', 'app.py'):
import_name = prepare_import(path) import_name = prepare_import(path)
app = locate_app( app = locate_app(self, import_name, None,
self, import_name, None, raise_if_not_found=False raise_if_not_found=False)
)
if app: if app:
break break
@ -357,8 +388,10 @@ class ScriptInfo(object):
debug = get_debug_flag() debug = get_debug_flag()
# Update the app's debug flag through the descriptor so that other
# values repopulate as well.
if debug is not None: if debug is not None:
app._reconfigure_for_run_debug(debug) app.debug = debug
self._loaded_app = app self._loaded_app = app
return app return app
@ -432,10 +465,8 @@ class FlaskGroup(AppGroup):
from :file:`.env` and :file:`.flaskenv` files. from :file:`.env` and :file:`.flaskenv` files.
""" """
def __init__( def __init__(self, add_default_commands=True, create_app=None,
self, add_default_commands=True, create_app=None, add_version_option=True, load_dotenv=True, **extra):
add_version_option=True, load_dotenv=True, **extra
):
params = list(extra.pop('params', None) or ()) params = list(extra.pop('params', None) or ())
if add_version_option: if add_version_option:
@ -578,11 +609,118 @@ def load_dotenv(path=None):
return new_dir is not None # at least one file was located and loaded return new_dir is not None # at least one file was located and loaded
def show_server_banner(env, debug, app_import_path):
"""Show extra startup messages the first time the server is run,
ignoring the reloader.
"""
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
return
if app_import_path is not None:
print(' * Serving Flask app "{0}"'.format(app_import_path))
print(' * Environment: {0}'.format(env))
if env == 'production':
click.secho(
' WARNING: Do not use the development server in a production'
' environment.', fg='red')
click.secho(' Use a production WSGI server instead.', dim=True)
if debug is not None:
print(' * Debug mode: {0}'.format('on' if debug else 'off'))
class CertParamType(click.ParamType):
"""Click option type for the ``--cert`` option. Allows either an
existing file, the string ``'adhoc'``, or an import for a
:class:`~ssl.SSLContext` object.
"""
name = 'path'
def __init__(self):
self.path_type = click.Path(
exists=True, dir_okay=False, resolve_path=True)
def convert(self, value, param, ctx):
try:
return self.path_type(value, param, ctx)
except click.BadParameter:
value = click.STRING(value, param, ctx).lower()
if value == 'adhoc':
try:
import OpenSSL
except ImportError:
raise click.BadParameter(
'Using ad-hoc certificates requires pyOpenSSL.',
ctx, param)
return value
obj = import_string(value, silent=True)
if sys.version_info < (2, 7):
if obj:
return obj
else:
if isinstance(obj, ssl.SSLContext):
return obj
raise
def _validate_key(ctx, param, value):
"""The ``--key`` option must be specified when ``--cert`` is a file.
Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
"""
cert = ctx.params.get('cert')
is_adhoc = cert == 'adhoc'
if sys.version_info < (2, 7):
is_context = cert and not isinstance(cert, (text_type, bytes))
else:
is_context = isinstance(cert, ssl.SSLContext)
if value is not None:
if is_adhoc:
raise click.BadParameter(
'When "--cert" is "adhoc", "--key" is not used.',
ctx, param)
if is_context:
raise click.BadParameter(
'When "--cert" is an SSLContext object, "--key is not used.',
ctx, param)
if not cert:
raise click.BadParameter(
'"--cert" must also be specified.',
ctx, param)
ctx.params['cert'] = cert, value
else:
if cert and not (is_adhoc or is_context):
raise click.BadParameter(
'Required when using "--cert".',
ctx, param)
return value
@click.command('run', short_help='Runs a development server.') @click.command('run', short_help='Runs a development server.')
@click.option('--host', '-h', default='127.0.0.1', @click.option('--host', '-h', default='127.0.0.1',
help='The interface to bind to.') help='The interface to bind to.')
@click.option('--port', '-p', default=5000, @click.option('--port', '-p', default=5000,
help='The port to bind to.') help='The port to bind to.')
@click.option('--cert', type=CertParamType(),
help='Specify a certificate file to use HTTPS.')
@click.option('--key',
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
callback=_validate_key, expose_value=False,
help='The key file to use when specifying a certificate.')
@click.option('--reload/--no-reload', default=None, @click.option('--reload/--no-reload', default=None,
help='Enable or disable the reloader. By default the reloader ' help='Enable or disable the reloader. By default the reloader '
'is active if debug is enabled.') 'is active if debug is enabled.')
@ -592,48 +730,36 @@ def load_dotenv(path=None):
@click.option('--eager-loading/--lazy-loader', default=None, @click.option('--eager-loading/--lazy-loader', default=None,
help='Enable or disable eager loading. By default eager ' help='Enable or disable eager loading. By default eager '
'loading is enabled if the reloader is disabled.') 'loading is enabled if the reloader is disabled.')
@click.option('--with-threads/--without-threads', default=False, @click.option('--with-threads/--without-threads', default=True,
help='Enable or disable multithreading.') help='Enable or disable multithreading.')
@pass_script_info @pass_script_info
def run_command(info, host, port, reload, debugger, eager_loading, def run_command(info, host, port, reload, debugger, eager_loading,
with_threads): with_threads, cert):
"""Runs a local development server for the Flask application. """Run a local development server.
This local server is recommended for development purposes only but it This server is for development purposes only. It does not provide
can also be used for simple intranet deployments. By default it will the stability, security, or performance of production WSGI servers.
not support any sort of concurrency at all to simplify debugging. This
can be changed with the --with-threads option which will enable basic
multithreading.
The reloader and debugger are by default enabled if the debug flag of The reloader and debugger are enabled by default if
Flask is enabled and disabled otherwise. FLASK_ENV=development or FLASK_DEBUG=1.
""" """
from werkzeug.serving import run_simple
debug = get_debug_flag() debug = get_debug_flag()
if reload is None: if reload is None:
reload = bool(debug) reload = debug
if debugger is None: if debugger is None:
debugger = bool(debug) debugger = debug
if eager_loading is None: if eager_loading is None:
eager_loading = not reload eager_loading = not reload
show_server_banner(get_env(), debug, info.app_import_path)
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
# Extra startup messages. This depends a bit on Werkzeug internals to from werkzeug.serving import run_simple
# not double execute when the reloader kicks in. run_simple(host, port, app, use_reloader=reload, use_debugger=debugger,
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': threaded=with_threads, ssl_context=cert)
# If we have an import path we can print it out now which can help
# people understand what's being served. If we do not have an
# import path because the app was loaded through a callback then
# we won't print anything.
if info.app_import_path is not None:
print(' * Serving Flask app "%s"' % info.app_import_path)
if debug is not None:
print(' * Forcing debug mode %s' % (debug and 'on' or 'off'))
run_simple(host, port, app, use_reloader=reload,
use_debugger=debugger, threaded=with_threads)
@click.command('shell', short_help='Runs a shell in the app context.') @click.command('shell', short_help='Runs a shell in the app context.')
@ -649,11 +775,11 @@ def shell_command():
import code import code
from flask.globals import _app_ctx_stack from flask.globals import _app_ctx_stack
app = _app_ctx_stack.top.app app = _app_ctx_stack.top.app
banner = 'Python %s on %s\nApp: %s%s\nInstance: %s' % ( banner = 'Python %s on %s\nApp: %s [%s]\nInstance: %s' % (
sys.version, sys.version,
sys.platform, sys.platform,
app.import_name, app.import_name,
app.debug and ' [debug]' or '', app.env,
app.instance_path, app.instance_path,
) )
ctx = {} ctx = {}
@ -722,12 +848,12 @@ A general utility script for Flask applications.
Provides commands from Flask, extensions, and the application. Loads the Provides commands from Flask, extensions, and the application. Loads the
application defined in the FLASK_APP environment variable, or from a wsgi.py application defined in the FLASK_APP environment variable, or from a wsgi.py
file. Debug mode can be controlled with the FLASK_DEBUG file. Setting the FLASK_ENV environment variable to 'development' will enable
environment variable. debug mode.
\b \b
{prefix}{cmd} FLASK_APP=hello.py {prefix}{cmd} FLASK_APP=hello.py
{prefix}{cmd} FLASK_DEBUG=1 {prefix}{cmd} FLASK_ENV=development
{prefix}flask run {prefix}flask run
""".format( """.format(
cmd='export' if os.name == 'posix' else 'set', cmd='export' if os.name == 'posix' else 'set',

6
flask/config.py

@ -5,7 +5,7 @@
Implements the configuration related objects. Implements the configuration related objects.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -129,7 +129,9 @@ class Config(dict):
with open(filename, mode='rb') as config_file: with open(filename, mode='rb') as config_file:
exec(compile(config_file.read(), filename, 'exec'), d.__dict__) exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
except IOError as e: except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR): if silent and e.errno in (
errno.ENOENT, errno.EISDIR, errno.ENOTDIR
):
return False return False
e.strerror = 'Unable to load configuration file (%s)' % e.strerror e.strerror = 'Unable to load configuration file (%s)' % e.strerror
raise raise

2
flask/ctx.py

@ -5,7 +5,7 @@
Implements the objects required to keep the context. Implements the objects required to keep the context.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

3
flask/debughelpers.py

@ -5,9 +5,10 @@
Various helpers to make the development experience better. Various helpers to make the development experience better.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import os import os
from warnings import warn from warnings import warn

2
flask/globals.py

@ -6,7 +6,7 @@
Defines all the global objects that are proxies to the current Defines all the global objects that are proxies to the current
active context. active context.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

48
flask/helpers.py

@ -5,7 +5,7 @@
Implements various helpers. Implements various helpers.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -22,28 +22,18 @@ import unicodedata
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from functools import update_wrapper from functools import update_wrapper
try:
from werkzeug.urls import url_quote from werkzeug.urls import url_quote
except ImportError:
from urlparse import quote as url_quote
from werkzeug.datastructures import Headers, Range from werkzeug.datastructures import Headers, Range
from werkzeug.exceptions import BadRequest, NotFound, \ from werkzeug.exceptions import BadRequest, NotFound, \
RequestedRangeNotSatisfiable RequestedRangeNotSatisfiable
# this was moved in 0.7
try:
from werkzeug.wsgi import wrap_file from werkzeug.wsgi import wrap_file
except ImportError:
from werkzeug.utils import wrap_file
from jinja2 import FileSystemLoader from jinja2 import FileSystemLoader
from .signals import message_flashed from .signals import message_flashed
from .globals import session, _request_ctx_stack, _app_ctx_stack, \ from .globals import session, _request_ctx_stack, _app_ctx_stack, \
current_app, request current_app, request
from ._compat import string_types, text_type from ._compat import string_types, text_type, PY2
# sentinel # sentinel
_missing = object() _missing = object()
@ -56,10 +46,25 @@ _os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
if sep not in (None, '/')) if sep not in (None, '/'))
def get_debug_flag(default=None): def get_env():
"""Get the environment the app is running in, indicated by the
:envvar:`FLASK_ENV` environment variable. The default is
``'production'``.
"""
return os.environ.get('FLASK_ENV') or 'production'
def get_debug_flag():
"""Get whether debug mode should be enabled for the app, indicated
by the :envvar:`FLASK_DEBUG` environment variable. The default is
``True`` if :func:`.get_env` returns ``'development'``, or ``False``
otherwise.
"""
val = os.environ.get('FLASK_DEBUG') val = os.environ.get('FLASK_DEBUG')
if not val: if not val:
return default return get_env() == 'development'
return val.lower() not in ('0', 'false', 'no') return val.lower() not in ('0', 'false', 'no')
@ -603,17 +608,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
'headers' % filename, stacklevel=2) 'headers' % filename, stacklevel=2)
if conditional: if conditional:
if callable(getattr(Range, 'to_content_range_header', None)):
# Werkzeug supports Range Requests
# Remove this test when support for Werkzeug <0.12 is dropped
try: try:
rv = rv.make_conditional(request, accept_ranges=True, rv = rv.make_conditional(request, accept_ranges=True,
complete_length=fsize) complete_length=fsize)
except RequestedRangeNotSatisfiable: except RequestedRangeNotSatisfiable:
if file is not None:
file.close() file.close()
raise raise
else:
rv = rv.make_conditional(request)
# make sure we don't send x-sendfile for servers that # make sure we don't send x-sendfile for servers that
# ignore the 304 status code for x-sendfile. # ignore the 304 status code for x-sendfile.
if rv.status_code == 304: if rv.status_code == 304:
@ -1001,12 +1002,21 @@ def total_seconds(td):
def is_ip(value): def is_ip(value):
"""Determine if the given string is an IP address. """Determine if the given string is an IP address.
Python 2 on Windows doesn't provide ``inet_pton``, so this only
checks IPv4 addresses in that environment.
:param value: value to check :param value: value to check
:type value: str :type value: str
:return: True if string is an IP address :return: True if string is an IP address
:rtype: bool :rtype: bool
""" """
if PY2 and os.name == 'nt':
try:
socket.inet_aton(value)
return True
except socket.error:
return False
for family in (socket.AF_INET, socket.AF_INET6): for family in (socket.AF_INET, socket.AF_INET6):
try: try:

10
flask/json/__init__.py

@ -1,4 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
flask.json
~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import io import io
import uuid import uuid
from datetime import date, datetime from datetime import date, datetime
@ -264,7 +272,7 @@ def jsonify(*args, **kwargs):
data = args or kwargs data = args or kwargs
return current_app.response_class( return current_app.response_class(
(dumps(data, indent=indent, separators=separators), '\n'), dumps(data, indent=indent, separators=separators) + '\n',
mimetype=current_app.config['JSONIFY_MIMETYPE'] mimetype=current_app.config['JSONIFY_MIMETYPE']
) )

3
flask/json/tag.py

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
""" """
Tagged JSON Tagged JSON
~~~~~~~~~~~ ~~~~~~~~~~~
@ -37,6 +38,8 @@ processes dicts first, so insert the new tag at the front of the order since
app.session_interface.serializer.register(TagOrderedDict, 0) app.session_interface.serializer.register(TagOrderedDict, 0)
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
""" """
from base64 import b64decode, b64encode from base64 import b64decode, b64encode

9
flask/logging.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
flask.logging
~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import from __future__ import absolute_import
import logging import logging

77
flask/sessions.py

@ -5,11 +5,13 @@
Implements cookie based sessions based on itsdangerous. Implements cookie based sessions based on itsdangerous.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import hashlib import hashlib
import warnings import warnings
from collections import MutableMapping
from datetime import datetime from datetime import datetime
from itsdangerous import BadSignature, URLSafeTimedSerializer from itsdangerous import BadSignature, URLSafeTimedSerializer
@ -19,43 +21,55 @@ from flask.helpers import is_ip, total_seconds
from flask.json.tag import TaggedJSONSerializer from flask.json.tag import TaggedJSONSerializer
class SessionMixin(object): class SessionMixin(MutableMapping):
"""Expands a basic dictionary with an accessors that are expected """Expands a basic dictionary with session attributes."""
by Flask extensions and users for the session.
"""
def _get_permanent(self): @property
def permanent(self):
"""This reflects the ``'_permanent'`` key in the dict."""
return self.get('_permanent', False) return self.get('_permanent', False)
def _set_permanent(self, value): @permanent.setter
def permanent(self, value):
self['_permanent'] = bool(value) self['_permanent'] = bool(value)
#: this reflects the ``'_permanent'`` key in the dict. #: Some implementations can detect whether a session is newly
permanent = property(_get_permanent, _set_permanent) #: created, but that is not guaranteed. Use with caution. The mixin
del _get_permanent, _set_permanent # default is hard-coded ``False``.
#: some session backends can tell you if a session is new, but that is
#: not necessarily guaranteed. Use with caution. The default mixin
#: implementation just hardcodes ``False`` in.
new = False new = False
#: for some backends this will always be ``True``, but some backends will #: Some implementations can detect changes to the session and set
#: default this to false and detect changes in the dictionary for as #: this when that happens. The mixin default is hard coded to
#: long as changes do not happen on mutable structures in the session. #: ``True``.
#: The default mixin implementation just hardcodes ``True`` in.
modified = True modified = True
#: the accessed variable indicates whether or not the session object has #: Some implementations can detect when session data is read or
#: been accessed in that request. This allows flask to append a `Vary: #: written and set this when that happens. The mixin default is hard
#: Cookie` header to the response if the session is being accessed. This #: coded to ``True``.
#: allows caching proxy servers, like Varnish, to use both the URL and the
#: session cookie as keys when caching pages, preventing multiple users
#: from being served the same cache.
accessed = True accessed = True
class SecureCookieSession(CallbackDict, SessionMixin): class SecureCookieSession(CallbackDict, SessionMixin):
"""Base class for sessions based on signed cookies.""" """Base class for sessions based on signed cookies.
This session backend will set the :attr:`modified` and
:attr:`accessed` attributes. It cannot reliably track whether a
session is new (vs. empty), so :attr:`new` remains hard coded to
``False``.
"""
#: When data is changed, this is set to ``True``. Only the session
#: dictionary itself is tracked; if the session contains mutable
#: data (for example a nested dict) then this must be set to
#: ``True`` manually when modifying that data. The session cookie
#: will only be written to the response if this is ``True``.
modified = False
#: When data is read or written, this is set to ``True``. Used by
# :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
#: header, which allows caching proxies to cache different pages for
#: different users.
accessed = False
def __init__(self, initial=None): def __init__(self, initial=None):
def on_update(self): def on_update(self):
@ -63,8 +77,6 @@ class SecureCookieSession(CallbackDict, SessionMixin):
self.accessed = True self.accessed = True
super(SecureCookieSession, self).__init__(initial, on_update) super(SecureCookieSession, self).__init__(initial, on_update)
self.modified = False
self.accessed = False
def __getitem__(self, key): def __getitem__(self, key):
self.accessed = True self.accessed = True
@ -238,6 +250,13 @@ class SessionInterface(object):
""" """
return app.config['SESSION_COOKIE_SECURE'] return app.config['SESSION_COOKIE_SECURE']
def get_cookie_samesite(self, app):
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
``SameSite`` attribute. This currently just returns the value of
the :data:`SESSION_COOKIE_SAMESITE` setting.
"""
return app.config['SESSION_COOKIE_SAMESITE']
def get_expiration_time(self, app, session): def get_expiration_time(self, app, session):
"""A helper method that returns an expiration date for the session """A helper method that returns an expiration date for the session
or ``None`` if the session is linked to the browser session. The or ``None`` if the session is linked to the browser session. The
@ -351,6 +370,7 @@ class SecureCookieSessionInterface(SessionInterface):
httponly = self.get_cookie_httponly(app) httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app) secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session) expires = self.get_expiration_time(app, session)
val = self.get_signing_serializer(app).dumps(dict(session)) val = self.get_signing_serializer(app).dumps(dict(session))
response.set_cookie( response.set_cookie(
@ -360,5 +380,6 @@ class SecureCookieSessionInterface(SessionInterface):
httponly=httponly, httponly=httponly,
domain=domain, domain=domain,
path=path, path=path,
secure=secure secure=secure,
samesite=samesite
) )

3
flask/signals.py

@ -6,9 +6,10 @@
Implements signals based on blinker if available, otherwise Implements signals based on blinker if available, otherwise
falls silently back to a noop. falls silently back to a noop.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
signals_available = False signals_available = False
try: try:
from blinker import Namespace from blinker import Namespace

3
flask/templating.py

@ -5,9 +5,10 @@
Implements the bridge to Jinja2. Implements the bridge to Jinja2.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from jinja2 import BaseLoader, Environment as BaseEnvironment, \ from jinja2 import BaseLoader, Environment as BaseEnvironment, \
TemplateNotFound TemplateNotFound

42
flask/testing.py

@ -6,20 +6,19 @@
Implements test support helpers. This module is lazily imported Implements test support helpers. This module is lazily imported
and usually not used in production environments. and usually not used in production environments.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import werkzeug import werkzeug
from contextlib import contextmanager from contextlib import contextmanager
from click.testing import CliRunner
from flask.cli import ScriptInfo
from werkzeug.test import Client, EnvironBuilder from werkzeug.test import Client, EnvironBuilder
from flask import _request_ctx_stack from flask import _request_ctx_stack
from flask.json import dumps as json_dumps from flask.json import dumps as json_dumps
try:
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
except ImportError:
from urlparse import urlsplit as url_parse
def make_test_environ_builder( def make_test_environ_builder(
@ -197,3 +196,36 @@ class FlaskClient(Client):
top = _request_ctx_stack.top top = _request_ctx_stack.top
if top is not None and top.preserved: if top is not None and top.preserved:
top.pop() top.pop()
class FlaskCliRunner(CliRunner):
"""A :class:`~click.testing.CliRunner` for testing a Flask app's
CLI commands. Typically created using
:meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
"""
def __init__(self, app, **kwargs):
self.app = app
super(FlaskCliRunner, self).__init__(**kwargs)
def invoke(self, cli=None, args=None, **kwargs):
"""Invokes a CLI command in an isolated environment. See
:meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for
full method documentation. See :ref:`testing-cli` for examples.
If the ``obj`` argument is not given, passes an instance of
:class:`~flask.cli.ScriptInfo` that knows how to load the Flask
app being tested.
:param cli: Command object to invoke. Default is the app's
:attr:`~flask.app.Flask.cli` group.
:param args: List of strings to invoke the command with.
:return: a :class:`~click.testing.Result` object.
"""
if cli is None:
cli = self.app.cli
if 'obj' not in kwargs:
kwargs['obj'] = ScriptInfo(create_app=lambda: self.app)
return super(FlaskCliRunner, self).invoke(cli, args, **kwargs)

3
flask/views.py

@ -5,9 +5,10 @@
This module provides class-based views inspired by the ones in Django. This module provides class-based views inspired by the ones in Django.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from .globals import request from .globals import request
from ._compat import with_metaclass from ._compat import with_metaclass

3
flask/wrappers.py

@ -5,9 +5,10 @@
Implements the WSGI wrappers (request and response). Implements the WSGI wrappers (request and response).
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase

309
scripts/flaskext_tester.py

@ -1,309 +0,0 @@
# -*- coding: utf-8 -*-
"""
Flask Extension Tests
~~~~~~~~~~~~~~~~~~~~~
Tests the Flask extensions.
:copyright: (c) 2015 by Ali Afshar.
:license: BSD, see LICENSE for more details.
"""
import os
import sys
import shutil
import urllib2
import tempfile
import subprocess
import argparse
from flask import json
from setuptools.package_index import PackageIndex
from setuptools.archive_util import unpack_archive
flask_svc_url = 'http://flask.pocoo.org/extensions/'
# OS X has awful paths when using mkstemp or gettempdir(). I don't
# care about security or clashes here, so pick something that is
# actually memorable.
if sys.platform == 'darwin':
_tempdir = '/private/tmp'
else:
_tempdir = tempfile.gettempdir()
tdir = _tempdir + '/flaskext-test'
flaskdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
# virtualenv hack *cough*
os.environ['PYTHONDONTWRITEBYTECODE'] = ''
RESULT_TEMPATE = u'''\
<!doctype html>
<title>Flask-Extension Test Results</title>
<style type=text/css>
body { font-family: 'Georgia', serif; font-size: 17px; color: #000; }
a { color: #004B6B; }
a:hover { color: #6D4100; }
h1, h2, h3 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; }
h1 { font-size: 30px; margin: 15px 0 5px 0; }
h2 { font-size: 24px; margin: 15px 0 5px 0; }
h3 { font-size: 19px; margin: 15px 0 5px 0; }
textarea, code,
pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace!important; font-size: 15px;
background: #eee; }
pre { padding: 7px 15px; line-height: 1.3; }
p { line-height: 1.4; }
table { border: 1px solid black; border-collapse: collapse;
margin: 15px 0; }
td, th { border: 1px solid black; padding: 4px 10px;
text-align: left; }
th { background: #eee; font-weight: normal; }
tr.success { background: #D3F5CC; }
tr.failed { background: #F5D2CB; }
</style>
<h1>Flask-Extension Test Results</h1>
<p>
This page contains the detailed test results for the test run of
all {{ 'approved' if approved }} Flask extensions.
<h2>Summary</h2>
<table class=results>
<thead>
<tr>
<th>Extension
<th>Version
<th>Author
<th>License
<th>Outcome
{%- for iptr, _ in results[0].logs|dictsort %}
<th>{{ iptr }}
{%- endfor %}
</tr>
</thead>
<tbody>
{%- for result in results %}
{% set outcome = 'success' if result.success else 'failed' %}
<tr class={{ outcome }}>
<th>{{ result.name }}
<td>{{ result.version }}
<td>{{ result.author }}
<td>{{ result.license }}
<td>{{ outcome }}
{%- for iptr, _ in result.logs|dictsort %}
<td><a href="#{{ result.name }}-{{ iptr }}">see log</a>
{%- endfor %}
</tr>
{%- endfor %}
</tbody>
</table>
<h2>Test Logs</h2>
<p>Detailed test logs for all tests on all platforms:
{%- for result in results %}
{%- for iptr, log in result.logs|dictsort %}
<h3 id="{{ result.name }}-{{ iptr }}">
{{ result.name }} - {{ result.version }} [{{ iptr }}]</h3>
<pre>{{ log }}</pre>
{%- endfor %}
{%- endfor %}
'''
def log(msg, *args):
print('[EXTTEST] ' + (msg % args))
class TestResult(object):
def __init__(self, name, folder, statuscode, interpreters):
intrptr = os.path.join(folder, '.tox/%s/bin/python'
% interpreters[0])
self.statuscode = statuscode
self.folder = folder
self.success = statuscode == 0
def fetch(field):
try:
c = subprocess.Popen([intrptr, 'setup.py',
'--' + field], cwd=folder,
stdout=subprocess.PIPE)
return c.communicate()[0].strip()
except OSError:
return '?'
self.name = name
self.license = fetch('license')
self.author = fetch('author')
self.version = fetch('version')
self.logs = {}
for interpreter in interpreters:
logfile = os.path.join(folder, '.tox/%s/log/test.log'
% interpreter)
if os.path.isfile(logfile):
self.logs[interpreter] = open(logfile).read()
else:
self.logs[interpreter] = ''
def create_tdir():
try:
shutil.rmtree(tdir)
except Exception:
pass
os.mkdir(tdir)
def package_flask():
distfolder = tdir + '/.flask-dist'
c = subprocess.Popen(['python', 'setup.py', 'sdist', '--formats=gztar',
'--dist', distfolder], cwd=flaskdir)
c.wait()
return os.path.join(distfolder, os.listdir(distfolder)[0])
def get_test_command(checkout_dir):
if os.path.isfile(checkout_dir + '/Makefile'):
return 'make test'
return 'python setup.py test'
def fetch_extensions_list():
req = urllib2.Request(flask_svc_url, headers={'accept':'application/json'})
d = urllib2.urlopen(req).read()
data = json.loads(d)
for ext in data['extensions']:
yield ext
def checkout_extension(name):
log('Downloading extension %s to temporary folder', name)
root = os.path.join(tdir, name)
os.mkdir(root)
checkout_path = PackageIndex().download(name, root)
unpack_archive(checkout_path, root)
path = None
for fn in os.listdir(root):
path = os.path.join(root, fn)
if os.path.isdir(path):
break
log('Downloaded to %s', path)
return path
tox_template = """[tox]
envlist=%(env)s
[testenv]
deps=
%(deps)s
distribute
py
commands=bash flaskext-runtest.sh {envlogdir}/test.log
downloadcache=%(cache)s
"""
def create_tox_ini(checkout_path, interpreters, flask_dep):
tox_path = os.path.join(checkout_path, 'tox-flask-test.ini')
if not os.path.exists(tox_path):
with open(tox_path, 'w') as f:
f.write(tox_template % {
'env': ','.join(interpreters),
'cache': tdir,
'deps': flask_dep
})
return tox_path
def iter_extensions(only_approved=True):
for ext in fetch_extensions_list():
if ext['approved'] or not only_approved:
yield ext['name']
def test_extension(name, interpreters, flask_dep):
checkout_path = checkout_extension(name)
log('Running tests with tox in %s', checkout_path)
# figure out the test command and write a wrapper script. We
# can't write that directly into the tox ini because tox does
# not invoke the command from the shell so we have no chance
# to pipe the output into a logfile. The /dev/null hack is
# to trick py.test (if used) into not guessing widths from the
# invoking terminal.
test_command = get_test_command(checkout_path)
log('Test command: %s', test_command)
f = open(checkout_path + '/flaskext-runtest.sh', 'w')
f.write(test_command + ' &> "$1" < /dev/null\n')
f.close()
# if there is a tox.ini, remove it, it will cause troubles
# for us. Remove it if present, we are running tox ourselves
# afterall.
create_tox_ini(checkout_path, interpreters, flask_dep)
rv = subprocess.call(['tox', '-c', 'tox-flask-test.ini'], cwd=checkout_path)
return TestResult(name, checkout_path, rv, interpreters)
def run_tests(extensions, interpreters):
results = {}
create_tdir()
log('Packaging Flask')
flask_dep = package_flask()
log('Running extension tests')
log('Temporary Environment: %s', tdir)
for name in extensions:
log('Testing %s', name)
result = test_extension(name, interpreters, flask_dep)
if result.success:
log('Extension test succeeded')
else:
log('Extension test failed')
results[name] = result
return results
def render_results(results, approved):
from jinja2 import Template
items = results.values()
items.sort(key=lambda x: x.name.lower())
rv = Template(RESULT_TEMPATE, autoescape=True).render(results=items,
approved=approved)
fd, filename = tempfile.mkstemp(suffix='.html')
os.fdopen(fd, 'w').write(rv.encode('utf-8') + '\n')
return filename
def main():
parser = argparse.ArgumentParser(description='Runs Flask extension tests')
parser.add_argument('--all', dest='all', action='store_true',
help='run against all extensions, not just approved')
parser.add_argument('--browse', dest='browse', action='store_true',
help='show browser with the result summary')
parser.add_argument('--env', dest='env', default='py25,py26,py27',
help='the tox environments to run against')
parser.add_argument('--extension=', dest='extension', default=None,
help='tests a single extension')
args = parser.parse_args()
if args.extension is not None:
only_approved = False
extensions = [args.extension]
else:
only_approved = not args.all
extensions = iter_extensions(only_approved)
results = run_tests(extensions, [x.strip() for x in args.env.split(',')])
filename = render_results(results, only_approved)
if args.browse:
import webbrowser
webbrowser.open('file:///' + filename.lstrip('/'))
print('Results written to {}'.format(filename))
if __name__ == '__main__':
main()

84
scripts/make-release.py

@ -1,42 +1,41 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
make-release
~~~~~~~~~~~~
Helper script that performs a release. Does pretty much everything
automatically for us.
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from __future__ import print_function from __future__ import print_function
import sys
import os import os
import re import re
from datetime import datetime, date import sys
from subprocess import Popen, PIPE from datetime import date, datetime
from subprocess import PIPE, Popen
_date_clean_re = re.compile(r'(\d+)(st|nd|rd|th)') _date_strip_re = re.compile(r'(?<=\d)(st|nd|rd|th)')
def parse_changelog(): def parse_changelog():
with open('CHANGES') as f: with open('CHANGES.rst') as f:
lineiter = iter(f) lineiter = iter(f)
for line in lineiter: for line in lineiter:
match = re.search('^Version\s+(.*)', line.strip()) match = re.search('^Version\s+(.*)', line.strip())
if match is None: if match is None:
continue continue
version = match.group(1).strip() version = match.group(1).strip()
if lineiter.next().count('-') != len(match.group(0)):
if next(lineiter).count('-') != len(match.group(0)):
continue continue
while 1: while 1:
change_info = lineiter.next().strip() change_info = next(lineiter).strip()
if change_info: if change_info:
break break
match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)' match = re.search(
r'(?:, codename (.*))?(?i)', change_info) r'released on (\w+\s+\d+\w+\s+\d+)(?:, codename (.*))?',
change_info,
flags=re.IGNORECASE
)
if match is None: if match is None:
continue continue
@ -46,15 +45,16 @@ def parse_changelog():
def bump_version(version): def bump_version(version):
try: try:
parts = map(int, version.split('.')) parts = [int(i) for i in version.split('.')]
except ValueError: except ValueError:
fail('Current version is not numeric') fail('Current version is not numeric')
parts[-1] += 1 parts[-1] += 1
return '.'.join(map(str, parts)) return '.'.join(map(str, parts))
def parse_date(string): def parse_date(string):
string = _date_clean_re.sub(r'\1', string) string = _date_strip_re.sub('', string)
return datetime.strptime(string, '%B %d %Y') return datetime.strptime(string, '%B %d %Y')
@ -65,9 +65,13 @@ def set_filename_version(filename, version_number, pattern):
before, old, after = match.groups() before, old, after = match.groups()
changed.append(True) changed.append(True)
return before + version_number + after return before + version_number + after
with open(filename) as f: with open(filename) as f:
contents = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern, contents = re.sub(
inject_version, f.read()) r"^(\s*%s\s*=\s*')(.+?)(')" % pattern,
inject_version, f.read(),
flags=re.DOTALL | re.MULTILINE
)
if not changed: if not changed:
fail('Could not find %s in %s', pattern, filename) fail('Could not find %s in %s', pattern, filename)
@ -81,8 +85,9 @@ def set_init_version(version):
set_filename_version('flask/__init__.py', version, '__version__') set_filename_version('flask/__init__.py', version, '__version__')
def build_and_upload(): def build():
Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel', 'upload']).wait() cmd = [sys.executable, 'setup.py', 'sdist', 'bdist_wheel']
Popen(cmd).wait()
def fail(message, *args): def fail(message, *args):
@ -95,7 +100,9 @@ def info(message, *args):
def get_git_tags(): def get_git_tags():
return set(Popen(['git', 'tag'], stdout=PIPE).communicate()[0].splitlines()) return set(
Popen(['git', 'tag'], stdout=PIPE).communicate()[0].splitlines()
)
def git_is_clean(): def git_is_clean():
@ -116,29 +123,40 @@ def main():
os.chdir(os.path.join(os.path.dirname(__file__), '..')) os.chdir(os.path.join(os.path.dirname(__file__), '..'))
rv = parse_changelog() rv = parse_changelog()
if rv is None: if rv is None:
fail('Could not parse changelog') fail('Could not parse changelog')
version, release_date, codename = rv version, release_date, codename = rv
dev_version = bump_version(version) + '-dev' dev_version = bump_version(version) + '.dev'
info('Releasing %s (codename %s, release date %s)', info(
version, codename, release_date.strftime('%d/%m/%Y')) 'Releasing %s (codename %s, release date %s)',
version, codename, release_date.strftime('%d/%m/%Y')
)
tags = get_git_tags() tags = get_git_tags()
if version in tags: if version in tags:
fail('Version "%s" is already tagged', version) fail('Version "%s" is already tagged', version)
if release_date.date() != date.today(): if release_date.date() != date.today():
fail('Release date is not today (%s != %s)', fail(
release_date.date(), date.today()) 'Release date is not today (%s != %s)',
release_date.date(), date.today()
)
if not git_is_clean(): if not git_is_clean():
fail('You have uncommitted changes in git') fail('You have uncommitted changes in git')
try:
import wheel # noqa: F401
except ImportError:
fail('You need to install the wheel package.')
set_init_version(version) set_init_version(version)
make_git_commit('Bump version number to %s', version) make_git_commit('Bump version number to %s', version)
make_git_tag(version) make_git_tag(version)
build_and_upload() build()
set_init_version(dev_version) set_init_version(dev_version)

2
setup.cfg

@ -1,5 +1,5 @@
[aliases] [aliases]
release = egg_info -RDb '' release = egg_info -Db ''
[bdist_wheel] [bdist_wheel]
universal = 1 universal = 1

97
setup.py

@ -1,112 +1,67 @@
""" #!/usr/bin/env python
Flask # -*- coding: utf-8 -*-
----- import io
Flask is a microframework for Python based on Werkzeug, Jinja 2 and good
intentions. And before you ask: It's BSD licensed!
Flask is Fun
````````````
Save in a hello.py:
.. code:: python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
And Easy to Setup
`````````````````
And run it:
.. code:: bash
$ pip install Flask
$ python hello.py
* Running on http://localhost:5000/
Ready for production? `Read this first <http://flask.pocoo.org/docs/deploying/>`.
Links
`````
* `website <http://flask.pocoo.org/>`_
* `documentation <http://flask.pocoo.org/docs/>`_
* `development version
<https://github.com/pallets/flask/zipball/master#egg=Flask-dev>`_
"""
import re import re
import ast
from setuptools import setup from setuptools import setup
_version_re = re.compile(r'__version__\s+=\s+(.*)') with io.open('README.rst', 'rt', encoding='utf8') as f:
readme = f.read()
with open('flask/__init__.py', 'rb') as f: with io.open('flask/__init__.py', 'rt', encoding='utf8') as f:
version = str(ast.literal_eval(_version_re.search( version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1)
f.read().decode('utf-8')).group(1)))
setup( setup(
name='Flask', name='Flask',
version=version, version=version,
url='https://github.com/pallets/flask/', url='https://www.palletsprojects.com/p/flask/',
license='BSD', license='BSD',
author='Armin Ronacher', author='Armin Ronacher',
author_email='armin.ronacher@active-4.com', author_email='armin.ronacher@active-4.com',
description='A microframework based on Werkzeug, Jinja2 ' maintainer='Pallets team',
'and good intentions', maintainer_email='contact@palletsprojects.com',
long_description=__doc__, description='A simple framework for building complex web applications.',
long_description=readme,
packages=['flask', 'flask.json'], packages=['flask', 'flask.json'],
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
platforms='any', platforms='any',
install_requires=[ install_requires=[
'Werkzeug>=0.9', 'Werkzeug>=0.14',
'Jinja2>=2.4', 'Jinja2>=2.10',
'itsdangerous>=0.21', 'itsdangerous>=0.24',
'click>=4.0', 'click>=5.1',
], ],
extras_require={ extras_require={
'dotenv': ['python-dotenv'], 'dotenv': ['python-dotenv'],
'dev': [ 'dev': [
'blinker',
'python-dotenv',
'greenlet',
'pytest>=3', 'pytest>=3',
'coverage', 'coverage',
'tox', 'tox',
'sphinx', 'sphinx',
'sphinxcontrib-log-cabinet'
], ],
}, },
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment', 'Environment :: Web Environment',
'Framework :: Flask',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules' 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Topic :: Software Development :: Libraries :: Python Modules',
], ],
entry_points=''' entry_points={
[console_scripts] 'console_scripts': [
flask=flask.cli:main 'flask = flask.cli:main',
''' ],
},
) )

3
test-requirements.txt

@ -1,3 +0,0 @@
tox
pytest
pytest-cov

47
tests/conftest.py

@ -3,19 +3,56 @@
tests.conftest tests.conftest
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
:copyright: (c) 2015 by the Flask Team, see AUTHORS for more details. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import flask
import gc import gc
import os import os
import sys
import pkgutil import pkgutil
import pytest import sys
import textwrap import textwrap
import pytest
from _pytest import monkeypatch
import flask
from flask import Flask as _Flask from flask import Flask as _Flask
@pytest.fixture(scope='session', autouse=True)
def _standard_os_environ():
"""Set up ``os.environ`` at the start of the test session to have
standard values. Returns a list of operations that is used by
:func:`._reset_os_environ` after each test.
"""
mp = monkeypatch.MonkeyPatch()
out = (
(os.environ, 'FLASK_APP', monkeypatch.notset),
(os.environ, 'FLASK_ENV', monkeypatch.notset),
(os.environ, 'FLASK_DEBUG', monkeypatch.notset),
(os.environ, 'FLASK_RUN_FROM_CLI', monkeypatch.notset),
(os.environ, 'WERKZEUG_RUN_MAIN', monkeypatch.notset),
)
for _, key, value in out:
if value is monkeypatch.notset:
mp.delenv(key, False)
else:
mp.setenv(key, value)
yield out
mp.undo()
@pytest.fixture(autouse=True)
def _reset_os_environ(monkeypatch, _standard_os_environ):
"""Reset ``os.environ`` to the standard environ after each test,
in case a test changed something without cleaning up.
"""
monkeypatch._setitem.extend(_standard_os_environ)
class Flask(_Flask): class Flask(_Flask):
testing = True testing = True
secret_key = 'test key' secret_key = 'test key'
@ -23,7 +60,7 @@ class Flask(_Flask):
@pytest.fixture @pytest.fixture
def app(): def app():
app = Flask(__name__) app = Flask('flask_test', root_path=os.path.dirname(__file__))
return app return app

4
tests/test_appctx.py

@ -5,7 +5,7 @@
Tests the application context. Tests the application context.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -215,5 +215,5 @@ def test_clean_pop(app):
except ZeroDivisionError: except ZeroDivisionError:
pass pass
assert called == ['conftest', 'TEARDOWN'] assert called == ['flask_test', 'TEARDOWN']
assert not flask.current_app assert not flask.current_app

77
tests/test_basic.py

@ -5,7 +5,7 @@
The basic functionality. The basic functionality.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -221,12 +221,21 @@ def test_endpoint_decorator(app, client):
def test_session(app, client): def test_session(app, client):
@app.route('/set', methods=['POST']) @app.route('/set', methods=['POST'])
def set(): def set():
assert not flask.session.accessed
assert not flask.session.modified
flask.session['value'] = flask.request.form['value'] flask.session['value'] = flask.request.form['value']
assert flask.session.accessed
assert flask.session.modified
return 'value set' return 'value set'
@app.route('/get') @app.route('/get')
def get(): def get():
return flask.session['value'] assert not flask.session.accessed
assert not flask.session.modified
v = flask.session.get('value', 'None')
assert flask.session.accessed
assert not flask.session.modified
return v
assert client.post('/set', data={'value': '42'}).data == b'value set' assert client.post('/set', data={'value': '42'}).data == b'value set'
assert client.get('/get').data == b'42' assert client.get('/get').data == b'42'
@ -310,6 +319,7 @@ def test_session_using_session_settings(app, client):
SESSION_COOKIE_DOMAIN='.example.com', SESSION_COOKIE_DOMAIN='.example.com',
SESSION_COOKIE_HTTPONLY=False, SESSION_COOKIE_HTTPONLY=False,
SESSION_COOKIE_SECURE=True, SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_SAMESITE='Lax',
SESSION_COOKIE_PATH='/' SESSION_COOKIE_PATH='/'
) )
@ -324,6 +334,34 @@ def test_session_using_session_settings(app, client):
assert 'path=/' in cookie assert 'path=/' in cookie
assert 'secure' in cookie assert 'secure' in cookie
assert 'httponly' not in cookie assert 'httponly' not in cookie
assert 'samesite' in cookie
def test_session_using_samesite_attribute(app, client):
@app.route('/')
def index():
flask.session['testing'] = 42
return 'Hello World'
app.config.update(SESSION_COOKIE_SAMESITE='invalid')
with pytest.raises(ValueError):
client.get('/')
app.config.update(SESSION_COOKIE_SAMESITE=None)
rv = client.get('/')
cookie = rv.headers['set-cookie'].lower()
assert 'samesite' not in cookie
app.config.update(SESSION_COOKIE_SAMESITE='Strict')
rv = client.get('/')
cookie = rv.headers['set-cookie'].lower()
assert 'samesite=strict' in cookie
app.config.update(SESSION_COOKIE_SAMESITE='Lax')
rv = client.get('/')
cookie = rv.headers['set-cookie'].lower()
assert 'samesite=lax' in cookie
def test_session_localhost_warning(recwarn, app, client): def test_session_localhost_warning(recwarn, app, client):
@ -1391,10 +1429,12 @@ def test_request_locals():
assert not flask.g assert not flask.g
def test_test_app_proper_environ(app, client): def test_test_app_proper_environ():
app = flask.Flask(__name__, subdomain_matching=True)
app.config.update( app.config.update(
SERVER_NAME='localhost.localdomain:5000' SERVER_NAME='localhost.localdomain:5000'
) )
client = app.test_client()
@app.route('/') @app.route('/')
def index(): def index():
@ -1745,8 +1785,10 @@ def test_g_iteration_protocol(app_ctx):
assert sorted(flask.g) == ['bar', 'foo'] assert sorted(flask.g) == ['bar', 'foo']
def test_subdomain_basic_support(app, client): def test_subdomain_basic_support():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'localhost.localdomain' app.config['SERVER_NAME'] = 'localhost.localdomain'
client = app.test_client()
@app.route('/') @app.route('/')
def normal_index(): def normal_index():
@ -1763,7 +1805,9 @@ def test_subdomain_basic_support(app, client):
assert rv.data == b'test index' assert rv.data == b'test index'
def test_subdomain_matching(app, client): def test_subdomain_matching():
app = flask.Flask(__name__, subdomain_matching=True)
client = app.test_client()
app.config['SERVER_NAME'] = 'localhost.localdomain' app.config['SERVER_NAME'] = 'localhost.localdomain'
@app.route('/', subdomain='<user>') @app.route('/', subdomain='<user>')
@ -1774,8 +1818,10 @@ def test_subdomain_matching(app, client):
assert rv.data == b'index for mitsuhiko' assert rv.data == b'index for mitsuhiko'
def test_subdomain_matching_with_ports(app, client): def test_subdomain_matching_with_ports():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'localhost.localdomain:3000' app.config['SERVER_NAME'] = 'localhost.localdomain:3000'
client = app.test_client()
@app.route('/', subdomain='<user>') @app.route('/', subdomain='<user>')
def index(user): def index(user):
@ -1785,6 +1831,25 @@ def test_subdomain_matching_with_ports(app, client):
assert rv.data == b'index for mitsuhiko' assert rv.data == b'index for mitsuhiko'
@pytest.mark.parametrize('matching', (False, True))
def test_subdomain_matching_other_name(matching):
app = flask.Flask(__name__, subdomain_matching=matching)
app.config['SERVER_NAME'] = 'localhost.localdomain:3000'
client = app.test_client()
@app.route('/')
def index():
return '', 204
# ip address can't match name
rv = client.get('/', 'http://127.0.0.1:3000/')
assert rv.status_code == 404 if matching else 204
# allow all subdomains if matching is disabled
rv = client.get('/', 'http://www.localhost.localdomain:3000/')
assert rv.status_code == 404 if matching else 204
def test_multi_route_rules(app, client): def test_multi_route_rules(app, client):
@app.route('/') @app.route('/')
@app.route('/<test>/') @app.route('/<test>/')

17
tests/test_blueprints.py

@ -5,7 +5,7 @@
Blueprints (and currently modules) Blueprints (and currently modules)
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -114,7 +114,20 @@ def test_blueprint_app_error_handling(app, client):
assert client.get('/nope').data == b'you shall not pass' assert client.get('/nope').data == b'you shall not pass'
def test_blueprint_url_definitions(app, client): def test_blueprint_prefix_slash(app, client):
bp = flask.Blueprint('test', __name__, url_prefix='/bar/')
@bp.route('/foo')
def foo():
return '', 204
app.register_blueprint(bp)
app.register_blueprint(bp, url_prefix='/spam/')
assert client.get('/bar/foo').status_code == 204
assert client.get('/spam/foo').status_code == 204
def test_blueprint_url_defaults(app, client):
bp = flask.Blueprint('test', __name__) bp = flask.Blueprint('test', __name__)
@bp.route('/foo', defaults={'baz': 42}) @bp.route('/foo', defaults={'baz': 42})

98
tests/test_cli.py

@ -3,18 +3,19 @@
tests.test_cli tests.test_cli
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
:copyright: (c) 2016 by the Flask Team, see AUTHORS for more details. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
#
# This file was part of Flask-CLI and was modified under the terms its license, # This file was part of Flask-CLI and was modified under the terms of
# the Revised BSD License. # its Revised BSD License. Copyright © 2015 CERN.
# Copyright (C) 2015 CERN.
#
from __future__ import absolute_import from __future__ import absolute_import
import os import os
import ssl
import sys import sys
import types
from functools import partial from functools import partial
import click import click
@ -23,9 +24,11 @@ from _pytest.monkeypatch import notset
from click.testing import CliRunner from click.testing import CliRunner
from flask import Flask, current_app from flask import Flask, current_app
from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, dotenv, \ from flask.cli import (
find_best_app, get_version, load_dotenv, locate_app, prepare_import, \ AppGroup, FlaskGroup, NoAppException, ScriptInfo, dotenv, find_best_app,
get_version, load_dotenv, locate_app, prepare_import, run_command,
with_appcontext with_appcontext
)
cwd = os.getcwd() cwd = os.getcwd()
test_path = os.path.abspath(os.path.join( test_path = os.path.abspath(os.path.join(
@ -33,19 +36,6 @@ test_path = os.path.abspath(os.path.join(
)) ))
@pytest.fixture(autouse=True)
def manage_os_environ(monkeypatch):
# can't use monkeypatch.delitem since we don't want to restore a value
os.environ.pop('FLASK_APP', None)
os.environ.pop('FLASK_DEBUG', None)
# use monkeypatch internals to force-delete environ keys
monkeypatch._setitem.extend((
(os.environ, 'FLASK_APP', notset),
(os.environ, 'FLASK_DEBUG', notset),
(os.environ, 'FLASK_RUN_FROM_CLI', notset),
))
@pytest.fixture @pytest.fixture
def runner(): def runner():
return CliRunner() return CliRunner()
@ -144,6 +134,13 @@ def test_find_best_app(test_apps):
pytest.raises(NoAppException, find_best_app, script_info, Module) pytest.raises(NoAppException, find_best_app, script_info, Module)
class Module:
@staticmethod
def create_app():
raise TypeError('bad bad factory!')
pytest.raises(TypeError, find_best_app, script_info, Module)
@pytest.mark.parametrize('value,path,result', ( @pytest.mark.parametrize('value,path,result', (
('test', cwd, 'test'), ('test', cwd, 'test'),
@ -475,3 +472,62 @@ def test_dotenv_optional(monkeypatch):
monkeypatch.chdir(test_path) monkeypatch.chdir(test_path)
load_dotenv() load_dotenv()
assert 'FOO' not in os.environ assert 'FOO' not in os.environ
def test_run_cert_path():
# no key
with pytest.raises(click.BadParameter):
run_command.make_context('run', ['--cert', __file__])
# no cert
with pytest.raises(click.BadParameter):
run_command.make_context('run', ['--key', __file__])
ctx = run_command.make_context(
'run', ['--cert', __file__, '--key', __file__])
assert ctx.params['cert'] == (__file__, __file__)
def test_run_cert_adhoc(monkeypatch):
monkeypatch.setitem(sys.modules, 'OpenSSL', None)
# pyOpenSSL not installed
with pytest.raises(click.BadParameter):
run_command.make_context('run', ['--cert', 'adhoc'])
# pyOpenSSL installed
monkeypatch.setitem(sys.modules, 'OpenSSL', types.ModuleType('OpenSSL'))
ctx = run_command.make_context('run', ['--cert', 'adhoc'])
assert ctx.params['cert'] == 'adhoc'
# no key with adhoc
with pytest.raises(click.BadParameter):
run_command.make_context('run', ['--cert', 'adhoc', '--key', __file__])
def test_run_cert_import(monkeypatch):
monkeypatch.setitem(sys.modules, 'not_here', None)
# ImportError
with pytest.raises(click.BadParameter):
run_command.make_context('run', ['--cert', 'not_here'])
# not an SSLContext
if sys.version_info >= (2, 7):
with pytest.raises(click.BadParameter):
run_command.make_context('run', ['--cert', 'flask'])
# SSLContext
if sys.version_info < (2, 7):
ssl_context = object()
else:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
monkeypatch.setitem(sys.modules, 'ssl_context', ssl_context)
ctx = run_command.make_context('run', ['--cert', 'ssl_context'])
assert ctx.params['cert'] is ssl_context
# no --key with SSLContext
with pytest.raises(click.BadParameter):
run_command.make_context(
'run', ['--cert', 'ssl_context', '--key', __file__])

3
tests/test_config.py

@ -3,11 +3,10 @@
tests.test_config tests.test_config
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
:copyright: (c) 2015 by the Flask Team, see AUTHORS for more details. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from datetime import timedelta from datetime import timedelta
import os import os
import textwrap import textwrap

46
tests/test_helpers.py

@ -5,7 +5,7 @@
Various helpers. Various helpers.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
@ -21,7 +21,7 @@ from werkzeug.http import http_date, parse_cache_control_header, \
import flask import flask
from flask._compat import StringIO, text_type from flask._compat import StringIO, text_type
from flask.helpers import get_debug_flag from flask.helpers import get_debug_flag, get_env
def has_encoding(name): def has_encoding(name):
@ -506,7 +506,7 @@ class TestSendfile(object):
@pytest.mark.skipif( @pytest.mark.skipif(
not callable(getattr(Range, 'to_content_range_header', None)), not callable(getattr(Range, 'to_content_range_header', None)),
reason="not implement within werkzeug" reason="not implemented within werkzeug"
) )
def test_send_file_range_request(self, app, client): def test_send_file_range_request(self, app, client):
@app.route('/') @app.route('/')
@ -563,7 +563,32 @@ class TestSendfile(object):
assert rv.status_code == 200 assert rv.status_code == 200
rv.close() rv.close()
@pytest.mark.skipif(
not callable(getattr(Range, 'to_content_range_header', None)),
reason="not implemented within werkzeug"
)
def test_send_file_range_request_xsendfile_invalid(self, app, client):
# https://github.com/pallets/flask/issues/2526
app.use_x_sendfile = True
@app.route('/')
def index():
return flask.send_file('static/index.html', conditional=True)
rv = client.get('/', headers={'Range': 'bytes=1000-'})
assert rv.status_code == 416
rv.close()
def test_attachment(self, app, req_ctx): def test_attachment(self, app, req_ctx):
app = flask.Flask(__name__)
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()
with open(os.path.join(app.root_path, 'static/index.html')) as f: with open(os.path.join(app.root_path, 'static/index.html')) as f:
rv = flask.send_file(f, as_attachment=True, rv = flask.send_file(f, as_attachment=True,
@ -861,7 +886,7 @@ class TestSafeJoin(object):
class TestHelpers(object): class TestHelpers(object):
@pytest.mark.parametrize('debug, expected_flag, expected_default_flag', [ @pytest.mark.parametrize('debug, expected_flag, expected_default_flag', [
('', None, True), ('', False, False),
('0', False, False), ('0', False, False),
('False', False, False), ('False', False, False),
('No', False, False), ('No', False, False),
@ -873,7 +898,18 @@ class TestHelpers(object):
assert get_debug_flag() is None assert get_debug_flag() is None
else: else:
assert get_debug_flag() == expected_flag assert get_debug_flag() == expected_flag
assert get_debug_flag(default=True) == expected_default_flag assert get_debug_flag() == expected_default_flag
@pytest.mark.parametrize('env, ref_env, debug', [
('', 'production', False),
('production', 'production', False),
('development', 'development', True),
('other', 'other', False),
])
def test_get_env(self, monkeypatch, env, ref_env, debug):
monkeypatch.setenv('FLASK_ENV', env)
assert get_debug_flag() == debug
assert get_env() == ref_env
def test_make_response(self): def test_make_response(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)

3
tests/test_instance_config.py

@ -3,9 +3,10 @@
tests.test_instance tests.test_instance
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2015 by the Flask Team, see AUTHORS for more details. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import os import os
import sys import sys

9
tests/test_json_tag.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
tests.test_json_tag
~~~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from datetime import datetime from datetime import datetime
from uuid import uuid4 from uuid import uuid4

18
tests/test_logging.py

@ -1,3 +1,12 @@
# -*- coding: utf-8 -*-
"""
tests.test_logging
~~~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
import logging import logging
import sys import sys
@ -9,14 +18,18 @@ from flask.logging import default_handler, has_level_handler, \
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def reset_logging(monkeypatch): def reset_logging(pytestconfig):
root_handlers = logging.root.handlers[:] root_handlers = logging.root.handlers[:]
logging.root.handlers = []
root_level = logging.root.level root_level = logging.root.level
logger = logging.getLogger('flask.app') logger = logging.getLogger('flask.app')
logger.handlers = [] logger.handlers = []
logger.setLevel(logging.NOTSET) logger.setLevel(logging.NOTSET)
logging_plugin = pytestconfig.pluginmanager.unregister(
name='logging-plugin')
yield yield
logging.root.handlers[:] = root_handlers logging.root.handlers[:] = root_handlers
@ -25,6 +38,9 @@ def reset_logging(monkeypatch):
logger.handlers = [] logger.handlers = []
logger.setLevel(logging.NOTSET) logger.setLevel(logging.NOTSET)
if logging_plugin:
pytestconfig.pluginmanager.register(logging_plugin, 'logging-plugin')
def test_logger(app): def test_logger(app):
assert app.logger.name == 'flask.app' assert app.logger.name == 'flask.app'

2
tests/test_regression.py

@ -5,7 +5,7 @@
Tests regressions. Tests regressions.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

2
tests/test_reqctx.py

@ -5,7 +5,7 @@
Tests the request context. Tests the request context.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

2
tests/test_signals.py

@ -5,7 +5,7 @@
Signalling. Signalling.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

2
tests/test_subclassing.py

@ -6,7 +6,7 @@
Test that certain behavior of flask can be customized by Test that certain behavior of flask can be customized by
subclasses. subclasses.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

2
tests/test_templating.py

@ -5,7 +5,7 @@
Template functionality Template functionality
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

58
tests/test_testing.py

@ -5,17 +5,19 @@
Test client and more. Test client and more.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import click
import pytest import pytest
import flask import flask
import werkzeug import werkzeug
from flask._compat import text_type from flask._compat import text_type
from flask.cli import ScriptInfo
from flask.json import jsonify from flask.json import jsonify
from flask.testing import make_test_environ_builder from flask.testing import make_test_environ_builder, FlaskCliRunner
def test_environ_defaults_from_config(app, client): def test_environ_defaults_from_config(app, client):
@ -112,9 +114,11 @@ def test_path_is_url(app):
assert eb.path == '/' assert eb.path == '/'
def test_blueprint_with_subdomain(app, client): def test_blueprint_with_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'example.com:1234' app.config['SERVER_NAME'] = 'example.com:1234'
app.config['APPLICATION_ROOT'] = '/foo' app.config['APPLICATION_ROOT'] = '/foo'
client = app.test_client()
bp = flask.Blueprint('company', __name__, subdomain='xxx') bp = flask.Blueprint('company', __name__, subdomain='xxx')
@ -302,8 +306,10 @@ def test_json_request_and_response(app, client):
assert rv.get_json() == json_data assert rv.get_json() == json_data
def test_subdomain(app, client): def test_subdomain():
app = flask.Flask(__name__, subdomain_matching=True)
app.config['SERVER_NAME'] = 'example.com' app.config['SERVER_NAME'] = 'example.com'
client = app.test_client()
@app.route('/', subdomain='<company_id>') @app.route('/', subdomain='<company_id>')
def view(company_id): def view(company_id):
@ -334,3 +340,47 @@ def test_nosubdomain(app, client):
assert 200 == response.status_code assert 200 == response.status_code
assert b'xxx' == response.data assert b'xxx' == response.data
def test_cli_runner_class(app):
runner = app.test_cli_runner()
assert isinstance(runner, FlaskCliRunner)
class SubRunner(FlaskCliRunner):
pass
app.test_cli_runner_class = SubRunner
runner = app.test_cli_runner()
assert isinstance(runner, SubRunner)
def test_cli_invoke(app):
@app.cli.command('hello')
def hello_command():
click.echo('Hello, World!')
runner = app.test_cli_runner()
# invoke with command name
result = runner.invoke(args=['hello'])
assert 'Hello' in result.output
# invoke with command object
result = runner.invoke(hello_command)
assert 'Hello' in result.output
def test_cli_custom_obj(app):
class NS(object):
called = False
def create_app():
NS.called = True
return app
@app.cli.command('hello')
def hello_command():
click.echo('Hello, World!')
script_info = ScriptInfo(create_app=create_app)
runner = app.test_cli_runner()
runner.invoke(hello_command, obj=script_info)
assert NS.called

8
tests/test_user_error_handler.py

@ -1,4 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
tests.test_user_error_handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details.
"""
from werkzeug.exceptions import ( from werkzeug.exceptions import (
Forbidden, Forbidden,
InternalServerError, InternalServerError,

2
tests/test_views.py

@ -5,7 +5,7 @@
Pluggable views. Pluggable views.
:copyright: (c) 2015 by Armin Ronacher. :copyright: © 2010 by the Pallets team.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """

22
tox.ini

@ -1,15 +1,14 @@
[tox] [tox]
envlist = envlist =
py{36,35,34,33,27,26,py} py{36,35,34,27,py}
py{36,27,py}-simplejson py{36,27,py}-simplejson
py{36,33,27,26,py}-devel py{36,27,py}-devel
py{36,33,27,26,py}-lowest py{36,27,py}-lowest
docs-html docs-html
coverage-report coverage-report
[testenv] [testenv]
passenv = LANG passenv = LANG
usedevelop = true
deps = deps =
pytest>=3 pytest>=3
coverage coverage
@ -17,10 +16,10 @@ deps =
blinker blinker
python-dotenv python-dotenv
lowest: Werkzeug==0.9 lowest: Werkzeug==0.14
lowest: Jinja2==2.4 lowest: Jinja2==2.10
lowest: itsdangerous==0.21 lowest: itsdangerous==0.24
lowest: Click==4.0 lowest: Click==5.1
devel: https://github.com/pallets/werkzeug/archive/master.tar.gz devel: https://github.com/pallets/werkzeug/archive/master.tar.gz
devel: https://github.com/pallets/markupsafe/archive/master.tar.gz devel: https://github.com/pallets/markupsafe/archive/master.tar.gz
@ -29,6 +28,7 @@ deps =
devel: https://github.com/pallets/click/archive/master.tar.gz devel: https://github.com/pallets/click/archive/master.tar.gz
simplejson: simplejson simplejson: simplejson
commands = commands =
# the examples need to be installed to test successfully # the examples need to be installed to test successfully
pip install -e examples/flaskr -q pip install -e examples/flaskr -q
@ -40,11 +40,11 @@ commands =
[testenv:docs-html] [testenv:docs-html]
deps = sphinx deps = sphinx
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
[testenv:docs-linkcheck] [testenv:docs-linkcheck]
deps = sphinx deps = sphinx
commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/linkcheck commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs {envtmpdir}/linkcheck
[testenv:coverage-report] [testenv:coverage-report]
deps = coverage deps = coverage
@ -59,8 +59,6 @@ passenv = CI TRAVIS TRAVIS_*
deps = codecov deps = codecov
skip_install = true skip_install = true
commands = commands =
# install argparse for 2.6
python -c 'import sys, pip; sys.version_info < (2, 7) and pip.main(["install", "argparse", "-q"])'
coverage combine coverage combine
coverage report coverage report
codecov codecov

Loading…
Cancel
Save