Browse Source

Merge branch 'master' into json-object-hook

pull/2352/head
David Lord 7 years ago
parent
commit
ea2e9609bc
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
  1. 35
      .github/ISSUE_TEMPLATE.rst
  2. 16
      .github/PULL_REQUEST_TEMPLATE.rst
  3. 3
      .gitmodules
  4. 42
      .travis.yml
  5. 44
      CHANGES
  6. 185
      CONTRIBUTING.rst
  7. 36
      Makefile
  8. 1
      docs/_themes
  9. 56
      docs/appcontext.rst
  10. 21
      docs/conf.py
  11. 412
      docs/config.rst
  12. 1
      docs/contents.rst.inc
  13. 1
      docs/contributing.rst
  14. 71
      docs/errorhandling.rst
  15. 86
      docs/flaskext.py
  16. 102
      docs/security.rst
  17. 8
      examples/flaskr/README
  18. 1
      examples/flaskr/flaskr/__init__.py
  19. 0
      examples/flaskr/flaskr/blueprints/__init__.py
  20. 55
      examples/flaskr/flaskr/blueprints/flaskr.py
  21. 64
      examples/flaskr/flaskr/factory.py
  22. 4
      examples/flaskr/flaskr/templates/layout.html
  23. 2
      examples/flaskr/flaskr/templates/login.html
  24. 2
      examples/flaskr/flaskr/templates/show_entries.html
  25. 4
      examples/flaskr/setup.cfg
  26. 15
      examples/flaskr/setup.py
  27. 59
      examples/flaskr/tests/test_flaskr.py
  28. 2
      examples/minitwit/minitwit/minitwit.py
  29. 4
      examples/patterns/largerapp/yourapplication/__init__.py
  30. 129
      flask/app.py
  31. 93
      flask/cli.py
  32. 6
      flask/ctx.py
  33. 14
      flask/helpers.py
  34. 7
      flask/sessions.py
  35. 45
      flask/testing.py
  36. 14
      setup.py
  37. 15
      tests/test_apps/cliapp/factory.py
  38. 35
      tests/test_basic.py
  39. 38
      tests/test_cli.py
  40. 2
      tests/test_helpers.py
  41. 32
      tests/test_testing.py
  42. 17
      tox.ini

35
.github/ISSUE_TEMPLATE.rst

@ -1,2 +1,33 @@
The issue tracker is a tool to address bugs.
Please use the #pocoo IRC channel on freenode or Stack Overflow for questions.
**This issue tracker is a tool to address bugs in Flask itself.
Please use the #pocoo IRC channel on freenode or Stack Overflow for general
questions about using Jinja or issues not related to Jinja.**
If you'd like to report a bug in Flask, fill out the template below. Provide
any any extra information that may be useful / related to your problem.
Ideally, create an [MCVE](http://stackoverflow.com/help/mcve), which helps us
understand the problem and helps check that it is not caused by something in
your code.
---
### Expected Behavior
Tell us what should happen.
```python
Paste a minimal example that causes the problem.
```
### Actual Behavior
Tell us what happens instead.
```pytb
Paste the full traceback if there was an exception.
```
### Environment
* Python version:
* Flask version:
* Werkzeug version:

16
.github/PULL_REQUEST_TEMPLATE.rst

@ -0,0 +1,16 @@
Describe what this patch does to fix the issue.
Link to any relevant issues or pull requests.
<!--
Commit checklist:
* add tests that fail without the patch
* ensure all tests pass with ``pytest``
* add documentation to the relevant docstrings or pages
* add ``versionadded`` or ``versionchanged`` directives to relevant docstrings
* add a changelog entry if this patch changes code
Tests, coverage, and docs will be run automatically when you submit the pull
request, but running them yourself can save time.
-->

3
.gitmodules vendored

@ -1,3 +0,0 @@
[submodule "docs/_themes"]
path = docs/_themes
url = https://github.com/mitsuhiko/flask-sphinx-themes.git

42
.travis.yml

@ -4,49 +4,25 @@ language: python
matrix:
include:
- python: 3.6
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: 3.5
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: 3.4
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: 3.3
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: 2.7
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: 2.6
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: pypy
env: TOXENV=py-release,codecov
env: TOXENV=py,codecov
- python: nightly
env: TOXENV=py-release
env: TOXENV=py
- python: 3.6
env: TOXENV=docs-html
- python: 3.6
env: TOXENV=py-release-simplejson,codecov
- python: 2.7
env: TOXENV=py-release-simplejson,codecov
- python: pypy
env: TOXENV=py-release-simplejson,codecov
- python: 3.6
env: TOXENV=py-devel,codecov
- python: 3.3
env: TOXENV=py-devel,codecov
- python: 2.7
env: TOXENV=py-devel,codecov
- python: 2.6
env: TOXENV=py-devel,codecov
- python: pypy
env: TOXENV=py-devel,codecov
- python: 3.6
env: TOXENV=py-lowest,codecov
- python: 3.3
env: TOXENV=py-lowest,codecov
- python: 2.7
env: TOXENV=py-lowest,codecov
- python: 2.6
env: TOXENV=py-lowest,codecov
- python: pypy
env: TOXENV=py-lowest,codecov
env: TOXENV=py-simplejson,codecov
install:
- pip install tox

44
CHANGES

@ -8,16 +8,19 @@ Version 0.13
Major release, unreleased
- Make `app.run()` into a noop if a Flask application is run from the
development server on the command line. This avoids some behavior that
- Minimum Werkzeug version bumped to 0.9, but please use the latest version.
- Minimum Click version bumped to 4, but please use the latest version.
- Make ``app.run()`` into a noop if a Flask application is run from the
development server on the command line. This avoids some behavior that
was confusing to debug for newcomers.
- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify()
method returns compressed response by default, and pretty response in
debug mode.
- Change Flask.__init__ to accept two new keyword arguments, ``host_matching``
and ``static_host``. This enables ``host_matching`` to be set properly by the
time the constructor adds the static route, and enables the static route to
be properly associated with the required host. (``#1559``)
- Change default configuration ``JSONIFY_PRETTYPRINT_REGULAR=False``.
``jsonify()`` method returns compressed response by default, and pretty
response in debug mode. (`#2193`_)
- Change ``Flask.__init__`` to accept two new keyword arguments,
``host_matching`` and ``static_host``. This enables ``host_matching`` to be
set properly by the time the constructor adds the static route, and enables
the static route to be properly associated with the required host.
(``#1559``)
- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_)
- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``.
(`#2017`_)
@ -46,18 +49,41 @@ Major release, unreleased
work with the ``flask`` command. If they take a single parameter or a
parameter named ``script_info``, the ``ScriptInfo`` object 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, for
example ``FLASK_APP=myproject.app:create_app('dev')``. (`#2326`_)
- ``View.provide_automatic_options = True`` is set on the view function from
``View.as_view``, to be detected in ``app.add_url_rule``. (`#2316`_)
- Error handling will try handlers registered for ``blueprint, code``,
``app, code``, ``blueprint, exception``, ``app, exception``. (`#2314`_)
- ``Cookie`` is added to the response's ``Vary`` header if the session is
accessed at all during the request (and it wasn't deleted). (`#2288`_)
- ``app.test_request_context()`` take ``subdomain`` and ``url_scheme``
parameters for use when building base URL. (`#1621`_)
- Set ``APPLICATION_ROOT = '/'`` by default. This was already the implicit
default when it was set to ``None``.
- ``TRAP_BAD_REQUEST_ERRORS`` is enabled by default in debug mode.
``BadRequestKeyError`` has a message with the bad key in debug mode instead
of the generic bad request message. (`#2348`_)
.. _#1489: https://github.com/pallets/flask/pull/1489
.. _#1621: https://github.com/pallets/flask/pull/1621
.. _#1898: https://github.com/pallets/flask/pull/1898
.. _#1936: https://github.com/pallets/flask/pull/1936
.. _#2017: https://github.com/pallets/flask/pull/2017
.. _#2193: https://github.com/pallets/flask/pull/2193
.. _#2223: https://github.com/pallets/flask/pull/2223
.. _#2254: https://github.com/pallets/flask/pull/2254
.. _#2256: https://github.com/pallets/flask/pull/2256
.. _#2259: https://github.com/pallets/flask/pull/2259
.. _#2282: https://github.com/pallets/flask/pull/2282
.. _#2288: https://github.com/pallets/flask/pull/2288
.. _#2297: https://github.com/pallets/flask/pull/2297
.. _#2314: https://github.com/pallets/flask/pull/2314
.. _#2316: https://github.com/pallets/flask/pull/2316
.. _#2319: https://github.com/pallets/flask/pull/2319
.. _#2326: https://github.com/pallets/flask/pull/2326
.. _#2348: https://github.com/pallets/flask/pull/2348
Version 0.12.2
--------------

185
CONTRIBUTING.rst

@ -1,46 +1,75 @@
==========================
How to contribute to Flask
==========================
Thanks for considering contributing to Flask.
Thank you for considering contributing to Flask!
Support questions
=================
-----------------
Please, don't use the issue tracker for this. Use one of the following
resources for questions about your own code:
* The IRC channel ``#pocoo`` on FreeNode.
* The IRC channel ``#python`` on FreeNode for more general questions.
* The mailing list flask@python.org for long term discussion or larger issues.
* Ask on `Stack Overflow`_. Search with Google first using:
``site:stackoverflow.com flask {search term, exception message, etc.}``
Please, don't use the issue tracker for this. Check whether the ``#pocoo`` IRC
channel on Freenode can help with your issue. If your problem is not strictly
Werkzeug or Flask specific, ``#python`` is generally more active.
`Stack Overflow <https://stackoverflow.com/>`_ is also worth considering.
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/flask?sort=linked
Reporting issues
================
----------------
- Under which versions of Python does this happen? This is even more important
if your issue is encoding related.
- Describe what you expected to happen.
- If possible, include a `minimal, complete, and verifiable example`_ to help
us identify the issue. This also helps check that the issue is not with your
own code.
- Describe what actually happened. Include the full traceback if there was an
exception.
- List your Python, Flask, and Werkzeug versions. If possible, check if this
issue is already fixed in the repository.
- Under which versions of Werkzeug does this happen? Check if this issue is
fixed in the repository.
.. _minimal, complete, and verifiable example: https://stackoverflow.com/help/mcve
Submitting patches
==================
------------------
- Include tests if your patch is supposed to solve a bug, and explain
clearly under which circumstances the bug happens. Make sure the test fails
without your patch.
- Try to follow `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_, but you
may ignore the line-length-limit if following it would make the code uglier.
- Try to follow `PEP8`_, but you may ignore the line length limit if following
it would make the code uglier.
First time setup
----------------
~~~~~~~~~~~~~~~~
- Download and install the `latest version of git`_.
- Configure git with your `username`_ and `email`_.
- Configure git with your `username`_ and `email`_::
git config --global user.name 'your name'
git config --global user.email 'your email'
- Make sure you have a `GitHub account`_.
- Fork Flask to your GitHub account by clicking the `Fork`_ button.
- `Clone`_ your GitHub fork locally.
- Add the main repository as a remote to update later.
``git remote add pallets https://github.com/pallets/flask``
- `Clone`_ your GitHub fork locally::
git clone https://github.com/{username}/flask
cd flask
- Add the main repository as a remote to update later::
git remote add pallets https://github.com/pallets/flask
git fetch pallets
- Create a virtualenv::
python3 -m venv env
. env/bin/activate
# or "env\Scripts\activate" on Windows
- Install Flask in editable mode with development dependencies::
pip install -e ".[dev]"
.. _GitHub account: https://github.com/join
.. _latest version of git: https://git-scm.com/downloads
@ -50,7 +79,7 @@ First time setup
.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork
Start coding
------------
~~~~~~~~~~~~
- Create a branch to identify the issue you would like to work on (e.g.
``2287-dry-test-suite``)
@ -68,98 +97,70 @@ Start coding
.. _contributing-testsuite:
Running the testsuite
---------------------
Running the tests
~~~~~~~~~~~~~~~~~
You probably want to set up a `virtualenv
<https://virtualenv.readthedocs.io/en/latest/index.html>`_.
Run the basic test suite with::
The minimal requirement for running the testsuite is ``pytest``. You can
install it with::
pytest
pip install pytest
This only runs the tests for the current environment. Whether this is relevant
depends on which part of Flask you're working on. Travis-CI will run the full
suite when you submit your pull request.
Clone this repository::
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,
3.4, 3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run::
git clone https://github.com/pallets/flask.git
Install Flask as an editable package using the current source::
cd flask
pip install --editable .
Running the testsuite
---------------------
The minimal requirement for running the testsuite is ``pytest``. You can
install it with::
pip install pytest
Then you can run the testsuite with::
pytest tests/
**Shortcut**: ``make test`` will ensure ``pytest`` is installed, and run it.
With only pytest installed, a large part of the testsuite will get skipped
though. Whether this is relevant depends on which part of Flask you're working
on. Travis is set up to run the full testsuite when you submit your pull
request anyways.
If you really want to test everything, you will have to install ``tox`` instead
of ``pytest``. You can install it with::
pip install tox
The ``tox`` command will then run all tests against multiple combinations
Python versions and dependency versions.
**Shortcut**: ``make tox-test`` will ensure ``tox`` is installed, and run it.
tox
Running test coverage
---------------------
Generating a report of lines that do not have unit test coverage can indicate where
to start contributing. ``pytest`` integrates with ``coverage.py``, using the ``pytest-cov``
plugin. This assumes you have already run the testsuite (see previous section)::
~~~~~~~~~~~~~~~~~~~~~
pip install pytest-cov
Generating a report of lines that do not have test coverage can indicate
where to start contributing. Run ``pytest`` using ``coverage`` and generate a
report on the terminal and as an interactive HTML document::
After this has been installed, you can output a report to the command line using this command::
coverage run -m pytest
coverage report
coverage html
# then open htmlcov/index.html
pytest --cov=flask tests/
Read more about `coverage <https://coverage.readthedocs.io>`_.
Generate a HTML report can be done using this command::
Running the full test suite with ``tox`` will combine the coverage reports
from all runs.
pytest --cov-report html --cov=flask tests/
``make`` targets
~~~~~~~~~~~~~~~~
Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io
Flask provides a ``Makefile`` with various shortcuts. They will ensure that
all dependencies are installed.
**Shortcut**: ``make cov`` will ensure ``pytest-cov`` is installed, run it, display the results, *and* save the HTML report.
- ``make test`` runs the basic test suite with ``pytest``
- ``make cov`` runs the basic test suite with ``coverage``
- ``make test-all`` runs the full test suite with ``tox``
- ``make docs`` builds the HTML documentation
Caution: zero-padded file modes
-------------------------------
Caution
=======
pushing
-------
This repository contains several zero-padded file modes that may cause issues when pushing this repository to git hosts other than github. Fixing this is destructive to the commit history, so we suggest ignoring these warnings. If it fails to push and you're using a self-hosted git service like Gitlab, you can turn off repository checks in the admin panel.
This repository contains several zero-padded file modes that may cause issues
when pushing this repository to git hosts other than GitHub. Fixing this is
destructive to the commit history, so we suggest ignoring these warnings. If it
fails to push and you're using a self-hosted git service like GitLab, you can
turn off repository checks in the admin panel.
cloning
-------
The zero-padded file modes files above can cause issues while cloning, too. If you have
::
These files can also cause issues while cloning. If you have ::
[fetch]
fsckobjects = true
or
::
or ::
[receive]
fsckObjects = true
set in your git configuration file, cloning this repository will fail. The only solution is to set both of the above settings to false while cloning, and then setting them back to true after the cloning is finished.
set in your git configuration file, cloning this repository will fail. The only
solution is to set both of the above settings to false while cloning, and then
setting them back to true after the cloning is finished.

36
Makefile

@ -1,14 +1,28 @@
.PHONY: clean-pyc ext-test test tox-test test-with-mem upload-docs docs audit
.PHONY: all install-dev test coverage cov test-all tox docs audit release clean-pyc upload-docs ebook
all: clean-pyc test
all: test
test:
pip install -r test-requirements.txt
tox -e py-release
install-dev:
pip install -q -e .[dev]
cov:
pip install -r test-requirements.txt -q
FLASK_DEBUG= py.test --cov-report term --cov-report html --cov=flask --cov=examples tests examples
test: clean-pyc install-dev
pytest
coverage: clean-pyc install-dev
pip install -q -e .[test]
coverage run -m pytest
coverage report
coverage html
cov: coverage
test-all: install-dev
tox
tox: test-all
docs: clean-pyc install-dev
$(MAKE) -C docs html
audit:
python setup.py audit
@ -16,9 +30,6 @@ audit:
release:
python scripts/make-release.py
ext-test:
python tests/flaskext_test.py --browse
clean-pyc:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
@ -40,6 +51,3 @@ ebook:
@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'
docs:
$(MAKE) -C docs html

1
docs/_themes

@ -1 +0,0 @@
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9

56
docs/appcontext.rst

@ -5,31 +5,37 @@ The Application Context
.. versionadded:: 0.9
One of the design ideas behind Flask is that there are two different
“states” in which code is executed. The application setup state in which
the application implicitly is on the module level. It starts when the
:class:`Flask` object is instantiated, and it implicitly ends when the
first request comes in. While the application is in this state a few
assumptions are true:
- the programmer can modify the application object safely.
- no request handling happened so far
- you have to have a reference to the application object in order to
modify it, there is no magic proxy that can give you a reference to
the application object you're currently creating or modifying.
In contrast, during request handling, a couple of other rules exist:
- while a request is active, the context local objects
(:data:`flask.request` and others) point to the current request.
- any code can get hold of these objects at any time.
There is a third state which is sitting in between a little bit.
Sometimes you are dealing with an application in a way that is similar to
how you interact with applications during request handling; just that there
is no request active. Consider, for instance, that you're sitting in an
interactive Python shell and interacting with the application, or a
command line application.
One of the design ideas behind Flask is that there are at least two
different “states” in which code is executed:
1. The application setup state, in which the application implicitly is
on the module level.
This state starts when the :class:`Flask` object is instantiated, and
it implicitly ends when the first request comes in. While the
application is in this state, a few assumptions are true:
- the programmer can modify the application object safely.
- no request handling happened so far
- you have to have a reference to the application object in order to
modify it, there is no magic proxy that can give you a reference to
the application object you're currently creating or modifying.
2. In contrast, in the request handling state, a couple of other rules
exist:
- while a request is active, the context local objects
(:data:`flask.request` and others) point to the current request.
- any code can get hold of these objects at any time.
3. There is also a third state somewhere in between 'module-level' and
'request-handling':
Sometimes you are dealing with an application in a way that is similar to
how you interact with applications during request handling, but without
there being an active request. Consider, for instance, that you're
sitting in an interactive Python shell and interacting with the
application, or a command line application.
The application context is what powers the :data:`~flask.current_app`
context local.

21
docs/conf.py

@ -22,7 +22,6 @@ BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.join(os.path.dirname(__file__), '_themes'))
sys.path.append(os.path.dirname(__file__))
# -- General configuration -----------------------------------------------------
@ -121,7 +120,7 @@ exclude_patterns = ['_build']
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
# html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@ -273,21 +272,9 @@ intersphinx_mapping = {
'blinker': ('https://pythonhosted.org/blinker/', None)
}
try:
__import__('flask_theme_support')
pygments_style = 'flask_theme_support.FlaskyStyle'
html_theme = 'flask'
html_theme_options = {
'touch_icon': 'touch-icon.png'
}
except ImportError:
print('-' * 74)
print('Warning: Flask themes unavailable. Building with default theme')
print('If you want the Flask themes, run this command and build again:')
print()
print(' git submodule update --init')
print('-' * 74)
html_theme_options = {
'touch_icon': 'touch-icon.png'
}
# unwrap decorators
def unwrap_decorators():

412
docs/config.rst

@ -3,8 +3,6 @@
Configuration Handling
======================
.. versionadded:: 0.3
Applications need some kind of configuration. There are different settings
you might want to change depending on the application environment like
toggling the debug mode, setting the secret key, and other such
@ -54,182 +52,253 @@ method::
$ flask run
(On Windows you need to use ``set`` instead of ``export``).
``app.debug`` and ``app.config['DEBUG']`` are not compatible with
``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
----------------------------
The following configuration values are used internally by Flask:
.. tabularcolumns:: |p{6.5cm}|p{8.5cm}|
================================= =========================================
``DEBUG`` enable/disable debug mode when using
``Flask.run()`` method to start server
``TESTING`` enable/disable testing mode
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
propagation of exceptions. If not set or
explicitly set to ``None`` this is
implicitly true if either ``TESTING`` or
``DEBUG`` is true.
``PRESERVE_CONTEXT_ON_EXCEPTION`` By default if the application is in
debug mode the request context is not
popped on exceptions to enable debuggers
to introspect the data. This can be
disabled by this key. You can also use
this setting to force-enable it for non
debug execution which might be useful to
debug production applications (but also
very risky).
``SECRET_KEY`` the secret key
``SESSION_COOKIE_NAME`` the name of the session cookie
``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If
this is not set, the cookie will be
valid for all subdomains of
``SERVER_NAME``.
``SESSION_COOKIE_PATH`` the path for the session cookie. If
this is not set the cookie will be valid
for all of ``APPLICATION_ROOT`` or if
that is not set for ``'/'``.
``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set
with the httponly flag. Defaults to
``True``.
``SESSION_COOKIE_SECURE`` controls if the cookie should be set
with the secure flag. Defaults to
``False``.
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
:class:`datetime.timedelta` object.
Starting with Flask 0.8 this can also be
an integer representing seconds.
``SESSION_REFRESH_EACH_REQUEST`` this flag controls how permanent
sessions are refreshed. If set to ``True``
(which is the default) then the cookie
is refreshed each request which
automatically bumps the lifetime. If
set to ``False`` a `set-cookie` header is
only sent if the session is modified.
Non permanent sessions are not affected
by this.
``USE_X_SENDFILE`` enable/disable x-sendfile
``LOGGER_NAME`` the name of the logger
``LOGGER_HANDLER_POLICY`` the policy of the default logging
handler. The default is ``'always'``
which means that the default logging
handler is always active. ``'debug'``
will only activate logging in debug
mode, ``'production'`` will only log in
production and ``'never'`` disables it
entirely.
``SERVER_NAME`` the name and port number of the server.
Required for subdomain support (e.g.:
``'myapp.dev:5000'``) Note that
localhost does not support subdomains so
setting this to “localhost” does not
help. Setting a ``SERVER_NAME`` also
by default enables URL generation
without a request context but with an
application context.
``APPLICATION_ROOT`` The path value used for the session
cookie if ``SESSION_COOKIE_PATH`` isn't
set. If it's also ``None`` ``'/'`` is used.
Note that to actually serve your Flask
app under a subpath you need to tell
your WSGI container the ``SCRIPT_NAME``
WSGI environment variable.
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
reject incoming requests with a
content length greater than this by
returning a 413 status code.
``SEND_FILE_MAX_AGE_DEFAULT`` Default cache control max age to use with
:meth:`~flask.Flask.send_static_file` (the
default static file handler) and
:func:`~flask.send_file`, as
:class:`datetime.timedelta` or as seconds.
Override this value on a per-file
basis using the
:meth:`~flask.Flask.get_send_file_max_age`
hook on :class:`~flask.Flask` or
:class:`~flask.Blueprint`,
respectively. Defaults to 43200 (12 hours).
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
not execute the error handlers of HTTP
exceptions but instead treat the
exception like any other and bubble it
through the exception stack. This is
helpful for hairy debugging situations
where you have to find out where an HTTP
exception is coming from.
``TRAP_BAD_REQUEST_ERRORS`` Werkzeug's internal data structures that
deal with request specific data will
raise special key errors that are also
bad request exceptions. Likewise many
operations can implicitly fail with a
BadRequest exception for consistency.
Since it's nice for debugging to know
why exactly it failed this flag can be
used to debug those situations. If this
config is set to ``True`` you will get
a regular traceback instead.
``PREFERRED_URL_SCHEME`` The URL scheme that should be used for
URL generation if no URL scheme is
available. This defaults to ``http``.
``JSON_AS_ASCII`` By default Flask serialize object to
ascii-encoded JSON. If this is set to
``False`` Flask will not encode to ASCII
and output strings as-is and return
unicode strings. ``jsonify`` will
automatically encode it in ``utf-8``
then for transport for instance.
``JSON_SORT_KEYS`` By default Flask will serialize JSON
objects in a way that the keys are
ordered. This is done in order to
ensure that independent of the hash seed
of the dictionary the return value will
be consistent to not trash external HTTP
caches. You can override the default
behavior by changing this variable.
This is not recommended but might give
you a performance improvement on the
cost of cacheability.
``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` or the Flask app
is running in debug mode, jsonify responses
will be pretty printed.
``JSONIFY_MIMETYPE`` MIME type used for jsonify responses.
``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of
the template source and reload it
automatically. By default the value is
``None`` which means that Flask checks
original file only in debug mode.
``EXPLAIN_TEMPLATE_LOADING`` If this is enabled then every attempt to
load a template will write an info
message to the logger explaining the
attempts to locate the template. This
can be useful to figure out why
templates cannot be found or wrong
templates appear to be loaded.
================================= =========================================
.. admonition:: More on ``SERVER_NAME``
The ``SERVER_NAME`` key is used for the subdomain support. Because
Flask cannot guess the subdomain part without the knowledge of the
actual server name, this is required if you want to work with
subdomains. This is also used for the session cookie.
Please keep in mind that not only Flask has the problem of not knowing
what subdomains are, your web browser does as well. Most modern web
browsers will not allow cross-subdomain cookies to be set on a
server name without dots in it. So if your server name is
``'localhost'`` you will not be able to set a cookie for
``'localhost'`` and every subdomain of it. Please choose a different
server name in that case, like ``'myapplication.local'`` and add
this name + the subdomains you want to use into your host config
or setup a local `bind`_.
.. _bind: https://www.isc.org/downloads/bind/
.. py:data:: DEBUG
Enable debug mode. When using the development server with ``flask run`` or
``app.run``, an interactive debugger will be shown for unhanlded
exceptions, and the server will be reloaded when code changes.
**Do not enable debug mode in production.**
Default: ``False``
.. py:data:: TESTING
Enable testing mode. Exceptions are propagated rather than handled by the
the app's error handlers. Extensions may also change their behavior to
facilitate easier testing. You should enable this in your own tests.
Default: ``False``
.. py:data:: PROPAGATE_EXCEPTIONS
Exceptions are re-raised rather than being handled by the app's error
handlers. If not set, this is implicitly true if ``TESTING`` or ``DEBUG``
is enabled.
Default: ``None``
.. py:data:: PRESERVE_CONTEXT_ON_EXCEPTION
Don't pop the request context when an exception occurs. If not set, this
is true if ``DEBUG`` is true. This allows debuggers to introspect the
request data on errors, and should normally not need to be set directly.
Default: ``None``
.. py:data:: TRAP_HTTP_EXCEPTIONS
If there is no handler for an ``HTTPException``-type exception, re-raise it
to be handled by the interactive debugger instead of returning it as a
simple error response.
Default: ``False``
.. py:data:: TRAP_BAD_REQUEST_ERRORS``
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
the error as an unhandled exception instead so that you get the interactive
debugger. This is a more specific version of ``TRAP_HTTP_EXCEPTIONS``. If
unset, it is enabled in debug mode.
Default: ``None``
.. py:data:: SECRET_KEY
A secret key that will be used for securely signing the session cookie
and can be used for any other security related needs by extensions or your
application. It should be a long random string of bytes, although unicode
is accepted too. For example, copy the output of this to your config::
python -c 'import os; print(os.urandom(32))'
**Do not reveal the secret key when posting questions or committing code.**
Default: ``None``
.. py:data:: SESSION_COOKIE_NAME
The name of the session cookie. Can be changed in case you already have a
cookie with the same name.
Default: ``'session'``
.. py:data:: SESSION_COOKIE_DOMAIN
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
``False``, the cookie's domain will not be set.
Default: ``None``
.. py:data:: SESSION_COOKIE_PATH
The path that the session cookie will be valid for. If not set, the cookie
will be valid underneath ``APPLICATION_ROOT`` or ``/`` if that is not set.
Default: ``None``
.. py:data:: SESSION_COOKIE_HTTPONLY
Browsers will not allow JavaScript access to cookies marked as "HTTP only"
for security.
Default: ``True``
.. py:data:: SESSION_COOKIE_SECURE
Browsers will only send cookies with requests over HTTPS if the cookie is
marked "secure". The application must be served over HTTPS for this to make
sense.
Default: ``False``
.. py:data:: PERMANENT_SESSION_LIFETIME
If ``session.permanent`` is true, the cookie's max age will be set to this
number of seconds. Can either be a :class:`datetime.timedelta` or an
``int``.
Default: ``timedelta(days=31)`` (``2678400`` seconds)
.. py:data:: SESSION_REFRESH_EACH_REQUEST
Control whether the cookie is sent with every response when
``session.permanent`` is true. Sending the cookie every time (the default)
can more reliably keep the session from expiring, but uses more bandwidth.
Non-permanent sessions are not affected.
Default: ``True``
.. py:data:: USE_X_SENDFILE
When serving files, set the ``X-Sendfile`` header instead of serving the
data with Flask. Some web servers, such as Apache, recognize this and serve
the data more efficiently. This only makes sense when using such a server.
Default: ``False``
.. py:data:: SEND_FILE_MAX_AGE_DEFAULT
When serving files, set the cache control max age to this number of
seconds. Can either be a :class:`datetime.timedelta` or an ``int``.
Override this value on a per-file basis using
:meth:`~flask.Flask.get_send_file_max_age` on the application or blueprint.
Default: ``timedelta(hours=12)`` (``43200`` seconds)
.. py:data:: LOGGER_NAME
The name of the logger that the Flask application sets up. If not set,
it will take the import name passed to ``Flask.__init__``.
Default: ``None``
.. py:data:: LOGGER_HANDLER_POLICY
When to activate the application's logger handler. ``'always'`` always
enables it, ``'debug'`` only activates it in debug mode, ``'production'``
only activates it when not in debug mode, and ``'never'`` never enables it.
Default: ``'always'``
.. py:data:: SERVER_NAME
Inform the application what host and port it is bound to. Required for
subdomain route matching support.
If set, will be used for the session cookie domain if
``SESSION_COOKIE_DOMAIN`` is not set. Modern web browsers will not allow
setting cookies for domains without a dot. To use a domain locally,
add any names that should route to the app to your ``hosts`` file. ::
127.0.0.1 localhost.dev
If set, ``url_for`` can generate external URLs with only an application
context instead of a request context.
Default: ``None``
.. py:data:: APPLICATION_ROOT
Inform the application what path it is mounted under by the application /
web server.
Will be used for the session cookie path if ``SESSION_COOKIE_PATH`` is not
set.
Default: ``'/'``
.. py:data:: PREFERRED_URL_SCHEME
Use this scheme for generating external URLs when not in a request context.
Default: ``'http'``
.. py:data:: MAX_CONTENT_LENGTH
Don't read more than this many bytes from the incoming request data. If not
set and the request does not specify a ``CONTENT_LENGTH``, no data will be
read for security.
Default: ``None``
.. py:data:: JSON_AS_ASCII
Serialize objects to ASCII-encoded JSON. If this is disabled, the JSON
will be returned as a Unicode string, or encoded as ``UTF-8`` by
``jsonify``. This has security implications when rendering the JSON in
to JavaScript in templates, and should typically remain enabled.
Default: ``True``
.. py:data:: JSON_SORT_KEYS
Sort the keys of JSON objects alphabetically. This is useful for caching
because it ensures the data is serialized the same way no matter what
Python's hash seed is. While not recommended, you can disable this for a
possible performance improvement at the cost of caching.
Default: ``True``
.. py:data:: JSONIFY_PRETTYPRINT_REGULAR
``jsonify`` responses will be output with newlines, spaces, and indentation
for easier reading by humans. Always enabled in debug mode.
Default: ``False``
.. py:data:: JSONIFY_MIMETYPE
The mimetype of ``jsonify`` responses.
Default: ``'application/json'``
.. py:data:: TEMPLATES_AUTO_RELOAD
Reload templates when they are changed. If not set, it will be enabled in
debug mode.
Default: ``None``
.. py:data:: EXPLAIN_TEMPLATE_LOADING
Log debugging information tracing how a template file was loaded. This can
be useful to figure out why a template was not loaded or the wrong file
appears to be loaded.
Default: ``False``
.. versionadded:: 0.4
``LOGGER_NAME``
@ -477,3 +546,4 @@ Example usage for both::
# or via open_instance_resource:
with app.open_instance_resource('application.cfg') as f:
config = f.read()

1
docs/contents.rst.inc

@ -59,3 +59,4 @@ Design notes, legal information and changelog are here for the interested.
upgrading
changelog
license
contributing

1
docs/contributing.rst

@ -0,0 +1 @@
.. include:: ../CONTRIBUTING.rst

71
docs/errorhandling.rst

@ -76,49 +76,72 @@ Error handlers
You might want to show custom error pages to the user when an error occurs.
This can be done by registering error handlers.
Error handlers are normal :ref:`views` but instead of being registered for
routes, they are registered for exceptions that are raised while trying to
do something else.
An error handler is a normal view function that return a response, but instead
of being registered for a route, it is registered for an exception or HTTP
status code that would is raised while trying to handle a request.
Registering
```````````
Register error handlers using :meth:`~flask.Flask.errorhandler` or
:meth:`~flask.Flask.register_error_handler`::
Register handlers by decorating a function with
:meth:`~flask.Flask.errorhandler`. Or use
:meth:`~flask.Flask.register_error_handler` to register the function later.
Remember to set the error code when returning the response. ::
@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
return 'bad request!'
return 'bad request!', 400
app.register_error_handler(400, lambda e: 'bad request!')
# or, without the decorator
app.register_error_handler(400, handle_bad_request)
Those two ways are equivalent, but the first one is more clear and leaves
you with a function to call on your whim (and in tests). Note that
:exc:`werkzeug.exceptions.HTTPException` subclasses like
:exc:`~werkzeug.exceptions.BadRequest` from the example and their HTTP codes
are interchangeable when handed to the registration methods or decorator
(``BadRequest.code == 400``).
:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable
when registering handlers. (``BadRequest.code == 400``)
You are however not limited to :exc:`~werkzeug.exceptions.HTTPException`
or HTTP status codes but can register a handler for every exception class you
like.
Non-standard HTTP codes cannot be registered by code because they are not known
by Werkzeug. Instead, define a subclass of
:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and
register and raise that exception class. ::
.. versionchanged:: 0.11
class InsufficientStorage(werkzeug.exceptions.HTTPException):
code = 507
description = 'Not enough storage space.'
app.register_error_handler(InsuffcientStorage, handle_507)
Errorhandlers are now prioritized by specificity of the exception classes
they are registered for instead of the order they are registered in.
raise InsufficientStorage()
Handlers can be registered for any exception class, not just
:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status
codes. Handlers can be registered for a specific class, or for all subclasses
of a parent class.
Handling
````````
Once an exception instance is raised, its class hierarchy is traversed,
and searched for in the exception classes for which handlers are registered.
The most specific handler is selected.
When an exception is caught by Flask while handling a request, it is first
looked up by code. If no handler is registered for the code, it is looked up
by its class hierarchy; the most specific handler is chosen. If no handler is
registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a
generic message about their code, while other exceptions are converted to a
generic 500 Internal Server Error.
E.g. if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
For example, if an instance of :exc:`ConnectionRefusedError` is raised, and a handler
is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`,
the more specific :exc:`ConnectionRefusedError` handler is called on the
exception instance, and its response is shown to the user.
the more specific :exc:`ConnectionRefusedError` handler is called with the
exception instance to generate the response.
Handlers registered on the blueprint take precedence over those registered
globally on the application, assuming a blueprint is handling the request that
raises the exception. However, the blueprint cannot handle 404 routing errors
because the 404 occurs at the routing level before the blueprint can be
determined.
.. versionchanged:: 0.11
Handlers are prioritized by specificity of the exception classes they are
registered for instead of the order they are registered in.
Error Mails
-----------

86
docs/flaskext.py

@ -1,86 +0,0 @@
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
class FlaskyStyle(Style):
background_color = "#f8f8f8"
default_style = ""
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}

102
docs/security.rst

@ -104,3 +104,105 @@ vulnerabilities
<https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so
this behavior was changed and :func:`~flask.jsonify` now supports serializing
arrays.
Security Headers
----------------
Browsers recognize various response headers in order to control security. We
recommend reviewing each of the headers below for use in your application.
The `Flask-Talisman`_ extension can be used to manage HTTPS and the security
headers for you.
.. _Flask-Talisman: https://github.com/GoogleCloudPlatform/flask-talisman
HTTP Strict Transport Security (HSTS)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tells the browser to convert all HTTP requests to HTTPS, preventing
man-in-the-middle (MITM) attacks. ::
response.haders['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
Content Security Policy (CSP)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tell the browser where it can load various types of resource from. This header
should be used whenever possible, but requires some work to define the correct
policy for your site. A very strict policy would be::
response.headers['Content-Security-Policy'] = "default-src: 'self'"
- https://csp.withgoogle.com/docs/index.html
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
X-Content-Type-Options
~~~~~~~~~~~~~~~~~~~~~~
Forces the browser to honor the response content type instead of trying to
detect it, which can be abused to generate a cross-site scripting (XSS)
attack. ::
response.headers['X-Content-Type-Options'] = 'nosniff'
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
X-Frame-Options
~~~~~~~~~~~~~~~
Prevents external sites from embedding your site in an ``iframe``. This
prevents a class of attacks where clicks in the outer frame can be translated
invisibly to clicks on your page's elements. This is also known as
"clickjacking". ::
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
X-XSS-Protection
~~~~~~~~~~~~~~~~
The browser will try to prevent reflected XSS attacks by not loading the page
if the request contains something that looks like JavaScript and the response
contains the same data. ::
response.headers['X-XSS-Protection'] = '1; mode=block'
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
Set-Cookie options
~~~~~~~~~~~~~~~~~~
These options can be added to a ``Set-Cookie`` header to improve their
security. Flask has configuration options to set these on the session cookie.
They can be set on other cookies too.
- ``Secure`` limits cookies to HTTPS traffic only.
- ``HttpOnly`` protects the contents of cookies from being read with
JavaScript.
- ``SameSite`` ensures that cookies can only be requested from the same
domain that created them. It is not supported by Flask yet.
::
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
)
response.set_cookie('username', 'flask', secure=True, httponly=True)
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies
HTTP Public Key Pinning (HPKP)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This tells the browser to authenticate with the server using only the specific
certificate key to prevent MITM attacks.
.. warning::
Be careful when enabling this, as it is very difficult to undo if you set up
or upgrade your key incorrectly.
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning

8
examples/flaskr/README

@ -9,9 +9,11 @@
~ How do I use it?
1. edit the configuration in the flaskr.py file or
1. edit the configuration in the factory.py file or
export an FLASKR_SETTINGS environment variable
pointing to a configuration file.
pointing to a configuration file or pass in a
dictionary with config values using the create_app
function.
2. install the app from the root of the project directory
@ -19,7 +21,7 @@
3. Instruct flask to use the right application
export FLASK_APP=flaskr
export FLASK_APP=flaskr.factory:create_app()
4. initialize the database with this command:

1
examples/flaskr/flaskr/__init__.py

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

0
examples/flaskr/flaskr/blueprints/__init__.py

55
examples/flaskr/flaskr/flaskr.py → examples/flaskr/flaskr/blueprints/flaskr.py

@ -10,29 +10,18 @@
:license: BSD, see LICENSE for more details.
"""
import os
from sqlite3 import dbapi2 as sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, \
render_template, flash
from flask import Blueprint, request, session, g, redirect, url_for, abort, \
render_template, flash, current_app
# create our little application :)
app = Flask(__name__)
# Load default config and override config from an environment variable
app.config.update(dict(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
DEBUG=True,
SECRET_KEY='development key',
USERNAME='admin',
PASSWORD='default'
))
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
# create our blueprint :)
bp = Blueprint('flaskr', __name__)
def connect_db():
"""Connects to the specific database."""
rv = sqlite3.connect(app.config['DATABASE'])
rv = sqlite3.connect(current_app.config['DATABASE'])
rv.row_factory = sqlite3.Row
return rv
@ -40,18 +29,11 @@ def connect_db():
def init_db():
"""Initializes the database."""
db = get_db()
with app.open_resource('schema.sql', mode='r') as f:
with current_app.open_resource('schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
@app.cli.command('initdb')
def initdb_command():
"""Creates the database tables."""
init_db()
print('Initialized the database.')
def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
@ -61,14 +43,7 @@ def get_db():
return g.sqlite_db
@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()
@app.route('/')
@bp.route('/')
def show_entries():
db = get_db()
cur = db.execute('select title, text from entries order by id desc')
@ -76,7 +51,7 @@ def show_entries():
return render_template('show_entries.html', entries=entries)
@app.route('/add', methods=['POST'])
@bp.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
@ -85,26 +60,26 @@ def add_entry():
[request.form['title'], request.form['text']])
db.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))
return redirect(url_for('flaskr.show_entries'))
@app.route('/login', methods=['GET', 'POST'])
@bp.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
if request.form['username'] != current_app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
elif request.form['password'] != current_app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('show_entries'))
return redirect(url_for('flaskr.show_entries'))
return render_template('login.html', error=error)
@app.route('/logout')
@bp.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
return redirect(url_for('flaskr.show_entries'))

64
examples/flaskr/flaskr/factory.py

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
Flaskr
~~~~~~
A microblog example application written as Flask tutorial with
Flask and sqlite3.
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
from flask import Flask, g
from werkzeug.utils import find_modules, import_string
from flaskr.blueprints.flaskr import init_db
def create_app(config=None):
app = Flask('flaskr')
app.config.update(dict(
DATABASE=os.path.join(app.root_path, 'flaskr.db'),
DEBUG=True,
SECRET_KEY='development key',
USERNAME='admin',
PASSWORD='default'
))
app.config.update(config or {})
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
register_blueprints(app)
register_cli(app)
register_teardowns(app)
return app
def register_blueprints(app):
"""Register all blueprint modules
Reference: Armin Ronacher, "Flask for Fun and for Profit" PyBay 2016.
"""
for name in find_modules('flaskr.blueprints'):
mod = import_string(name)
if hasattr(mod, 'bp'):
app.register_blueprint(mod.bp)
return None
def register_cli(app):
@app.cli.command('initdb')
def initdb_command():
"""Creates the database tables."""
init_db()
print('Initialized the database.')
def register_teardowns(app):
@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()

4
examples/flaskr/flaskr/templates/layout.html

@ -5,9 +5,9 @@
<h1>Flaskr</h1>
<div class="metanav">
{% if not session.logged_in %}
<a href="{{ url_for('login') }}">log in</a>
<a href="{{ url_for('flaskr.login') }}">log in</a>
{% else %}
<a href="{{ url_for('logout') }}">log out</a>
<a href="{{ url_for('flaskr.logout') }}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}

2
examples/flaskr/flaskr/templates/login.html

@ -2,7 +2,7 @@
{% block body %}
<h2>Login</h2>
{% if error %}<p class="error"><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method="post">
<form action="{{ url_for('flaskr.login') }}" method="post">
<dl>
<dt>Username:
<dd><input type="text" name="username">

2
examples/flaskr/flaskr/templates/show_entries.html

@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}
<form action="{{ url_for('add_entry') }}" method="post" class="add-entry">
<form action="{{ url_for('flaskr.add_entry') }}" method="post" class="add-entry">
<dl>
<dt>Title:
<dd><input type="text" size="30" name="title">

4
examples/flaskr/setup.cfg

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

15
examples/flaskr/setup.py

@ -1,8 +1,19 @@
from setuptools import setup
# -*- coding: utf-8 -*-
"""
Flaskr Tests
~~~~~~~~~~~~
Tests the Flaskr application.
:copyright: (c) 2015 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from setuptools import setup, find_packages
setup(
name='flaskr',
packages=['flaskr'],
packages=find_packages(),
include_package_data=True,
install_requires=[
'flask',

59
examples/flaskr/tests/test_flaskr.py

@ -12,19 +12,38 @@
import os
import tempfile
import pytest
from flaskr import flaskr
from flaskr.factory import create_app
from flaskr.blueprints.flaskr import init_db
@pytest.fixture
def client():
db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
flaskr.app.config['TESTING'] = True
client = flaskr.app.test_client()
with flaskr.app.app_context():
flaskr.init_db()
yield client
os.close(db_fd)
os.unlink(flaskr.app.config['DATABASE'])
def app(request):
db_fd, temp_db_location = tempfile.mkstemp()
config = {
'DATABASE': temp_db_location,
'TESTING': True,
'DB_FD': db_fd
}
app = create_app(config=config)
with app.app_context():
init_db()
yield app
@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
def login(client, username, password):
@ -44,25 +63,25 @@ def test_empty_db(client):
assert b'No entries here so far' in rv.data
def test_login_logout(client):
def test_login_logout(client, app):
"""Make sure login and logout works"""
rv = login(client, flaskr.app.config['USERNAME'],
flaskr.app.config['PASSWORD'])
rv = login(client, app.config['USERNAME'],
app.config['PASSWORD'])
assert b'You were logged in' in rv.data
rv = logout(client)
assert b'You were logged out' in rv.data
rv = login(client, flaskr.app.config['USERNAME'] + 'x',
flaskr.app.config['PASSWORD'])
rv = login(client,app.config['USERNAME'] + 'x',
app.config['PASSWORD'])
assert b'Invalid username' in rv.data
rv = login(client, flaskr.app.config['USERNAME'],
flaskr.app.config['PASSWORD'] + 'x')
rv = login(client, app.config['USERNAME'],
app.config['PASSWORD'] + 'x')
assert b'Invalid password' in rv.data
def test_messages(client):
def test_messages(client, app):
"""Test that messages work"""
login(client, flaskr.app.config['USERNAME'],
flaskr.app.config['PASSWORD'])
login(client, app.config['USERNAME'],
app.config['PASSWORD'])
rv = client.post('/add', data=dict(
title='<Hello>',
text='<strong>HTML</strong> allowed here'

2
examples/minitwit/minitwit/minitwit.py

@ -25,7 +25,7 @@ DEBUG = True
SECRET_KEY = 'development key'
# create our little application :)
app = Flask(__name__)
app = Flask('minitwit')
app.config.from_object(__name__)
app.config.from_envvar('MINITWIT_SETTINGS', silent=True)

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

@ -1,4 +1,4 @@
from flask import Flask
app = Flask(__name__)
app = Flask('yourapplication')
import yourapplication.views
import yourapplication.views

129
flask/app.py

@ -10,6 +10,7 @@
"""
import os
import sys
import warnings
from datetime import timedelta
from functools import update_wrapper
from itertools import chain
@ -17,7 +18,8 @@ from threading import Lock
from werkzeug.datastructures import ImmutableDict, Headers
from werkzeug.exceptions import BadRequest, HTTPException, \
InternalServerError, MethodNotAllowed, default_exceptions
InternalServerError, MethodNotAllowed, default_exceptions, \
BadRequestKeyError
from werkzeug.routing import BuildError, Map, RequestRedirect, Rule
from . import cli, json
@ -307,7 +309,7 @@ class Flask(_PackageBoundObject):
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'APPLICATION_ROOT': '/',
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
@ -316,7 +318,7 @@ class Flask(_PackageBoundObject):
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_BAD_REQUEST_ERRORS': None,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
@ -925,8 +927,17 @@ class Flask(_PackageBoundObject):
:attr:`secret_key` is set. Instead of overriding this method
we recommend replacing the :class:`session_interface`.
.. deprecated: 1.0
Will be removed in 1.1. Use ``session_interface.open_session``
instead.
:param request: an instance of :attr:`request_class`.
"""
warnings.warn(DeprecationWarning(
'"open_session" is deprecated and will be removed in 1.1. Use'
' "session_interface.open_session" instead.'
))
return self.session_interface.open_session(self, request)
def save_session(self, session, response):
@ -934,19 +945,37 @@ class Flask(_PackageBoundObject):
implementation, check :meth:`open_session`. Instead of overriding this
method we recommend replacing the :class:`session_interface`.
.. deprecated: 1.0
Will be removed in 1.1. Use ``session_interface.save_session``
instead.
:param session: the session to be saved (a
:class:`~werkzeug.contrib.securecookie.SecureCookie`
object)
:param response: an instance of :attr:`response_class`
"""
warnings.warn(DeprecationWarning(
'"save_session" is deprecated and will be removed in 1.1. Use'
' "session_interface.save_session" instead.'
))
return self.session_interface.save_session(self, session, response)
def make_null_session(self):
"""Creates a new instance of a missing session. Instead of overriding
this method we recommend replacing the :class:`session_interface`.
.. deprecated: 1.0
Will be removed in 1.1. Use ``session_interface.make_null_session``
instead.
.. versionadded:: 0.7
"""
warnings.warn(DeprecationWarning(
'"make_null_session" is deprecated and will be removed in 1.1. Use'
' "session_interface.make_null_session" instead.'
))
return self.session_interface.make_null_session(self)
@setupmethod
@ -1137,7 +1166,9 @@ class Flask(_PackageBoundObject):
@setupmethod
def errorhandler(self, code_or_exception):
"""A decorator that is used to register a function given an
"""Register a function to handle errors by code or exception class.
A decorator that is used to register a function given an
error code. Example::
@app.errorhandler(404)
@ -1150,21 +1181,6 @@ class Flask(_PackageBoundObject):
def special_exception_handler(error):
return 'Database connection failed', 500
You can also register a function as error handler without using
the :meth:`errorhandler` decorator. The following example is
equivalent to the one above::
def page_not_found(error):
return 'This page does not exist', 404
app.error_handler_spec[None][404] = page_not_found
Setting error handlers via assignments to :attr:`error_handler_spec`
however is discouraged as it requires fiddling with nested dictionaries
and the special case for arbitrary exception types.
The first ``None`` refers to the active blueprint. If the error
handler should be application wide ``None`` shall be used.
.. versionadded:: 0.7
Use :meth:`register_error_handler` instead of modifying
:attr:`error_handler_spec` directly, for application wide error
@ -1183,6 +1199,7 @@ class Flask(_PackageBoundObject):
return f
return decorator
@setupmethod
def register_error_handler(self, code_or_exception, f):
"""Alternative error attach function to the :meth:`errorhandler`
decorator that is more straightforward to use for non decorator
@ -1201,11 +1218,18 @@ class Flask(_PackageBoundObject):
"""
if isinstance(code_or_exception, HTTPException): # old broken behavior
raise ValueError(
'Tried to register a handler for an exception instance {0!r}. '
'Handlers can only be registered for exception classes or HTTP error codes.'
.format(code_or_exception))
'Tried to register a handler for an exception instance {0!r}.'
' Handlers can only be registered for exception classes or'
' HTTP error codes.'.format(code_or_exception)
)
exc_class, code = self._get_exc_class_and_code(code_or_exception)
try:
exc_class, code = self._get_exc_class_and_code(code_or_exception)
except KeyError:
raise KeyError(
"'{0}' is not a recognized HTTP error code. Use a subclass of"
" HTTPException with that code instead.".format(code_or_exception)
)
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
handlers[exc_class] = f
@ -1310,7 +1334,7 @@ class Flask(_PackageBoundObject):
@setupmethod
def before_request(self, f):
"""Registers a function to run before each request.
For example, this can be used to open a database connection, or to load
the logged in user from the session.
@ -1438,12 +1462,12 @@ class Flask(_PackageBoundObject):
"""Register a URL value preprocessor function for all view
functions in the application. These functions will be called before the
:meth:`before_request` functions.
The function can modify the values captured from the matched url before
they are passed to the view. For example, this can be used to pop a
common language code value and place it in ``g`` rather than pass it to
every view.
The function is passed the endpoint name and values dict. The return
value is ignored.
"""
@ -1515,12 +1539,20 @@ class Flask(_PackageBoundObject):
traceback. This is helpful for debugging implicitly raised HTTP
exceptions.
.. versionchanged:: 1.0
Bad request errors are not trapped by default in debug mode.
.. versionadded:: 0.8
"""
if self.config['TRAP_HTTP_EXCEPTIONS']:
return True
if self.config['TRAP_BAD_REQUEST_ERRORS']:
trap_bad_request = self.config['TRAP_BAD_REQUEST_ERRORS']
# if unset, trap based on debug mode
if (trap_bad_request is None and self.debug) or trap_bad_request:
return isinstance(e, BadRequest)
return False
def handle_user_exception(self, e):
@ -1531,16 +1563,30 @@ class Flask(_PackageBoundObject):
function will either return a response value or reraise the
exception with the same traceback.
.. versionchanged:: 1.0
Key errors raised from request data like ``form`` show the the bad
key in debug mode rather than a generic bad request message.
.. versionadded:: 0.7
"""
exc_type, exc_value, tb = sys.exc_info()
assert exc_value is e
# ensure not to trash sys.exc_info() at that point in case someone
# wants the traceback preserved in handle_http_exception. Of course
# we cannot prevent users from trashing it themselves in a custom
# trap_http_exception method so that's their fault then.
# MultiDict passes the key to the exception, but that's ignored
# when generating the response message. Set an informative
# description for key errors in debug mode or when trapping errors.
if (
(self.debug or self.config['TRAP_BAD_REQUEST_ERRORS'])
and isinstance(e, BadRequestKeyError)
# only set it if it's still the default description
and e.description is BadRequestKeyError.description
):
e.description = "KeyError: '{0}'".format(*e.args)
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e)
@ -1732,10 +1778,10 @@ class Flask(_PackageBoundObject):
``str`` (``unicode`` in Python 2)
A response object is created with the string encoded to UTF-8
as the body.
``bytes`` (``str`` in Python 2)
A response object is created with the bytes as the body.
``tuple``
Either ``(body, status, headers)``, ``(body, status)``, or
``(body, headers)``, where ``body`` is any of the other types
@ -1744,13 +1790,13 @@ class Flask(_PackageBoundObject):
tuples. If ``body`` is a :attr:`response_class` instance,
``status`` overwrites the exiting value and ``headers`` are
extended.
:attr:`response_class`
The object is returned unchanged.
other :class:`~werkzeug.wrappers.Response` class
The object is coerced to :attr:`response_class`.
:func:`callable`
The function is called as a WSGI application. The result is
used to create a response object.
@ -1845,7 +1891,7 @@ class Flask(_PackageBoundObject):
if self.config['SERVER_NAME'] is not None:
return self.url_map.bind(
self.config['SERVER_NAME'],
script_name=self.config['APPLICATION_ROOT'] or '/',
script_name=self.config['APPLICATION_ROOT'],
url_scheme=self.config['PREFERRED_URL_SCHEME'])
def inject_url_defaults(self, endpoint, values):
@ -1887,7 +1933,7 @@ class Flask(_PackageBoundObject):
:attr:`url_value_preprocessors` registered with the app and the
current blueprint (if any). Then calls :attr:`before_request_funcs`
registered with the app and the blueprint.
If any :meth:`before_request` handler returns a non-None value, the
value is handled as if it was the return value from the view, and
further request handling is stopped.
@ -1932,7 +1978,7 @@ class Flask(_PackageBoundObject):
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
self.save_session(ctx.session, response)
self.session_interface.save_session(self, ctx.session, response)
return response
def do_teardown_request(self, exc=_sentinel):
@ -2017,10 +2063,19 @@ class Flask(_PackageBoundObject):
def test_request_context(self, *args, **kwargs):
"""Creates a WSGI environment from the given values (see
:class:`werkzeug.test.EnvironBuilder` for more information, this
function accepts the same arguments).
function accepts the same arguments plus two additional).
Additional arguments (only if ``base_url`` is not specified):
:param subdomain: subdomain to use for route matching
:param url_scheme: scheme for the request, default
``PREFERRED_URL_SCHEME`` or ``http``.
"""
from flask.testing import make_test_environ_builder
builder = make_test_environ_builder(self, *args, **kwargs)
try:
return self.request_context(builder.get_environ())
finally:

93
flask/cli.py

@ -9,7 +9,10 @@
:license: BSD, see LICENSE for more details.
"""
import ast
import inspect
import os
import re
import sys
import traceback
from functools import update_wrapper
@ -55,20 +58,20 @@ def find_best_app(script_info, module):
' one.'.format(module=module.__name__)
)
# Search for app factory callables.
# Search for app factory functions.
for attr_name in ('create_app', 'make_app'):
app_factory = getattr(module, attr_name, None)
if callable(app_factory):
if inspect.isfunction(app_factory):
try:
app = call_factory(app_factory, script_info)
if isinstance(app, Flask):
return app
except TypeError:
raise NoAppException(
'Auto-detected "{callable}()" in module "{module}", but '
'Auto-detected "{function}()" in module "{module}", but '
'could not call it without specifying arguments.'.format(
callable=attr_name, module=module.__name__
function=attr_name, module=module.__name__
)
)
@ -79,18 +82,69 @@ def find_best_app(script_info, module):
)
def call_factory(func, script_info):
"""Checks if the given app factory function has an argument named
``script_info`` or just a single argument and calls the function passing
``script_info`` if so. Otherwise, calls the function without any arguments
and returns the result.
def call_factory(app_factory, script_info, arguments=()):
"""Takes an app factory, a ``script_info` object and optionally a tuple
of arguments. Checks for the existence of a script_info argument and calls
the app_factory depending on that and the arguments provided.
"""
arguments = getargspec(func).args
if 'script_info' in arguments:
return func(script_info=script_info)
elif len(arguments) == 1:
return func(script_info)
return func()
args_spec = getargspec(app_factory)
arg_names = args_spec.args
arg_defaults = args_spec.defaults
if 'script_info' in arg_names:
return app_factory(*arguments, script_info=script_info)
elif arguments:
return app_factory(*arguments)
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
return app_factory(script_info)
return app_factory()
def find_app_by_string(string, script_info, module):
"""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 ``script_info`` argument and calls the function with the appropriate
arguments."""
from . import Flask
function_regex = r'^(?P<name>\w+)(?:\((?P<args>.*)\))?$'
match = re.match(function_regex, string)
if match:
name, args = match.groups()
try:
if args is not None:
args = args.rstrip(' ,')
if args:
args = ast.literal_eval(
"({args}, )".format(args=args))
else:
args = ()
app_factory = getattr(module, name, None)
app = call_factory(app_factory, script_info, args)
else:
attr = getattr(module, name, None)
if inspect.isfunction(attr):
app = call_factory(attr, script_info)
else:
app = attr
if isinstance(app, Flask):
return app
else:
raise RuntimeError('Failed to find application in module '
'"{name}"'.format(name=module))
except TypeError as e:
new_error = NoAppException(
'{e}\nThe app factory "{factory}" in module "{module}" could'
' not be called with the specified arguments (and a'
' script_info argument automatically added if applicable).'
' Did you make sure to use the right number of arguments as'
' well as not using keyword arguments or'
' non-literals?'.format(e=e, factory=string, module=module))
reraise(NoAppException, new_error, sys.exc_info()[2])
else:
raise NoAppException(
'The provided string "{string}" is not a valid variable name'
'or function expression.'.format(string=string))
def prepare_exec_for_file(filename):
@ -148,14 +202,9 @@ def locate_app(script_info, app_id):
mod = sys.modules[module]
if app_obj is None:
app = find_best_app(script_info, mod)
return find_best_app(script_info, mod)
else:
app = getattr(mod, app_obj, None)
if app is None:
raise RuntimeError('Failed to find application in module "%s"'
% module)
return app
return find_app_by_string(app_obj, script_info, mod)
def find_default_import_path():

6
flask/ctx.py

@ -329,9 +329,11 @@ class RequestContext(object):
# available. This allows a custom open_session method to use the
# request context (e.g. code that access database information
# stored on `g` instead of the appcontext).
self.session = self.app.open_session(self.request)
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = self.app.make_null_session()
self.session = session_interface.make_null_session(self.app)
def pop(self, exc=_sentinel):
"""Pops the request context and unbinds it by doing that. This will

14
flask/helpers.py

@ -1004,17 +1004,3 @@ def is_ip(value):
return True
return False
def patch_vary_header(response, value):
"""Add a value to the ``Vary`` header if it is not already present."""
header = response.headers.get('Vary', '')
headers = [h for h in (h.strip() for h in header.split(',')) if h]
lower_value = value.lower()
if not any(h.lower() == lower_value for h in headers):
headers.append(value)
updated_header = ', '.join(headers)
response.headers['Vary'] = updated_header

7
flask/sessions.py

@ -18,7 +18,6 @@ from itsdangerous import BadSignature, URLSafeTimedSerializer
from werkzeug.datastructures import CallbackDict
from werkzeug.http import http_date, parse_date
from flask.helpers import patch_vary_header
from . import Markup, json
from ._compat import iteritems, text_type
from .helpers import is_ip, total_seconds
@ -347,8 +346,8 @@ class SessionInterface(object):
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
uses ``/`` if it's ``None``.
"""
return app.config['SESSION_COOKIE_PATH'] or \
app.config['APPLICATION_ROOT'] or '/'
return app.config['SESSION_COOKIE_PATH'] \
or app.config['APPLICATION_ROOT']
def get_cookie_httponly(self, app):
"""Returns True if the session cookie should be httponly. This
@ -466,7 +465,7 @@ class SecureCookieSessionInterface(SessionInterface):
# Add a "Vary: Cookie" header if the session was accessed at all.
if session.accessed:
patch_vary_header(response, 'Cookie')
response.vary.add('Cookie')
if not self.should_set_cookie(app, session):
return

45
flask/testing.py

@ -21,19 +21,37 @@ except ImportError:
from urlparse import urlsplit as url_parse
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
def make_test_environ_builder(
app, path='/', base_url=None, subdomain=None, url_scheme=None,
*args, **kwargs
):
"""Creates a new test builder with some application defaults thrown in."""
http_host = app.config.get('SERVER_NAME')
app_root = app.config.get('APPLICATION_ROOT')
assert (
not (base_url or subdomain or url_scheme)
or (base_url is not None) != bool(subdomain or url_scheme)
), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
if base_url is None:
http_host = app.config.get('SERVER_NAME') or 'localhost'
app_root = app.config['APPLICATION_ROOT']
if subdomain:
http_host = '{0}.{1}'.format(subdomain, http_host)
if url_scheme is None:
url_scheme = app.config['PREFERRED_URL_SCHEME']
url = url_parse(path)
base_url = 'http://%s/' % (url.netloc or http_host or 'localhost')
if app_root:
base_url += app_root.lstrip('/')
if url.netloc:
path = url.path
if url.query:
path += '?' + url.query
base_url = '{0}://{1}/{2}'.format(
url_scheme, url.netloc or http_host, app_root.lstrip('/')
)
path = url.path
if url.query:
sep = b'?' if isinstance(url.query, bytes) else '?'
path += sep + url.query
return EnvironBuilder(path, base_url, *args, **kwargs)
@ -87,7 +105,8 @@ class FlaskClient(Client):
self.cookie_jar.inject_wsgi(environ_overrides)
outer_reqctx = _request_ctx_stack.top
with app.test_request_context(*args, **kwargs) as c:
sess = app.open_session(c.request)
session_interface = app.session_interface
sess = session_interface.open_session(app, c.request)
if sess is None:
raise RuntimeError('Session backend did not open a session. '
'Check the configuration')
@ -106,8 +125,8 @@ class FlaskClient(Client):
_request_ctx_stack.pop()
resp = app.response_class()
if not app.session_interface.is_null_session(sess):
app.save_session(sess, resp)
if not session_interface.is_null_session(sess):
session_interface.save_session(app, sess, resp)
headers = resp.get_wsgi_headers(c.request.environ)
self.cookie_jar.extract_wsgi(c.request.environ, headers)

14
setup.py

@ -48,14 +48,12 @@ import re
import ast
from setuptools import setup
_version_re = re.compile(r'__version__\s+=\s+(.*)')
with open('flask/__init__.py', 'rb') as f:
version = str(ast.literal_eval(_version_re.search(
f.read().decode('utf-8')).group(1)))
setup(
name='Flask',
version=version,
@ -76,6 +74,17 @@ setup(
'itsdangerous>=0.21',
'click>=4.0',
],
extras_require={
'dev': [
'blinker',
'greenlet',
'pytest>=3',
'coverage',
'tox',
'sphinx',
'sphinxcontrib-log-cabinet'
],
},
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
@ -90,6 +99,7 @@ setup(
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries :: Python Modules'
],

15
tests/test_apps/cliapp/factory.py

@ -0,0 +1,15 @@
from __future__ import absolute_import, print_function
from flask import Flask
def create_app():
return Flask('create_app')
def create_app2(foo, bar):
return Flask("_".join(['create_app2', foo, bar]))
def create_app3(foo, bar, script_info):
return Flask("_".join(['create_app3', foo, bar]))

35
tests/test_basic.py

@ -9,20 +9,21 @@
:license: BSD, see LICENSE for more details.
"""
import pytest
import pickle
import re
import uuid
import time
import flask
import pickle
import uuid
from datetime import datetime
from threading import Thread
from flask._compat import text_type
from werkzeug.exceptions import BadRequest, NotFound, Forbidden
import pytest
import werkzeug.serving
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
from werkzeug.http import parse_date
from werkzeug.routing import BuildError
import werkzeug.serving
import flask
from flask._compat import text_type
def test_options_work(app, client):
@ -529,14 +530,14 @@ def test_session_vary_cookie(app, client):
@app.route('/vary-cookie-header-set')
def vary_cookie_header_set():
response = flask.Response()
response.headers['Vary'] = 'Cookie'
response.vary.add('Cookie')
flask.session['test'] = 'test'
return response
@app.route('/vary-header-set')
def vary_header_set():
response = flask.Response()
response.headers['Vary'] = 'Accept-Encoding, Accept-Language'
response.vary.update(('Accept-Encoding', 'Accept-Language'))
flask.session['test'] = 'test'
return response
@ -875,6 +876,13 @@ def test_error_handling(app, client):
assert b'forbidden' == rv.data
def test_error_handler_unknown_code(app):
with pytest.raises(KeyError) as exc_info:
app.register_error_handler(999, lambda e: ('999', 999))
assert 'Use a subclass' in exc_info.value.args[0]
def test_error_handling_processing(app, client):
app.config['LOGGER_HANDLER_POLICY'] = 'never'
app.testing = False
@ -980,12 +988,17 @@ def test_trapping_of_bad_request_key_errors(app, client):
def fail():
flask.request.form['missing_key']
assert client.get('/fail').status_code == 400
rv = client.get('/fail')
assert rv.status_code == 400
assert b'missing_key' not in rv.data
app.config['TRAP_BAD_REQUEST_ERRORS'] = True
with pytest.raises(KeyError) as e:
client.get("/fail")
assert e.errisinstance(BadRequest)
assert 'missing_key' in e.value.description
def test_trapping_of_all_http_exceptions(app, client):

38
tests/test_cli.py

@ -150,15 +150,37 @@ def test_locate_app(test_apps):
script_info = ScriptInfo()
assert locate_app(script_info, "cliapp.app").name == "testapp"
assert locate_app(script_info, "cliapp.app:testapp").name == "testapp"
assert locate_app(script_info, "cliapp.factory").name == "create_app"
assert locate_app(
script_info, "cliapp.factory").name == "create_app"
assert locate_app(
script_info, "cliapp.factory:create_app").name == "create_app"
assert locate_app(
script_info, "cliapp.factory:create_app()").name == "create_app"
assert locate_app(
script_info, "cliapp.factory:create_app2('foo', 'bar')"
).name == "create_app2_foo_bar"
assert locate_app(
script_info, "cliapp.factory:create_app2('foo', 'bar', )"
).name == "create_app2_foo_bar"
assert locate_app(
script_info, "cliapp.factory:create_app3('baz', 'qux')"
).name == "create_app3_baz_qux"
assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1"
pytest.raises(NoAppException, locate_app,
script_info, "notanpp.py")
pytest.raises(NoAppException, locate_app,
script_info, "cliapp/app")
pytest.raises(RuntimeError, locate_app,
script_info, "cliapp.app:notanapp")
pytest.raises(NoAppException, locate_app,
script_info, "cliapp.importerrorapp")
pytest.raises(
NoAppException, locate_app, script_info, "notanpp.py")
pytest.raises(
NoAppException, locate_app, script_info, "cliapp/app")
pytest.raises(
RuntimeError, locate_app, script_info, "cliapp.app:notanapp")
pytest.raises(
NoAppException, locate_app,
script_info, "cliapp.factory:create_app2('foo')")
pytest.raises(
NoAppException, locate_app,
script_info, "cliapp.factory:create_app ()")
pytest.raises(
NoAppException, locate_app, script_info, "cliapp.importerrorapp")
def test_find_default_import_path(test_apps, monkeypatch, tmpdir):

2
tests/test_helpers.py

@ -44,6 +44,7 @@ class TestJSON(object):
def test_post_empty_json_adds_exception_to_response_content_in_debug(self, app, client):
app.config['DEBUG'] = True
app.config['TRAP_BAD_REQUEST_ERRORS'] = False
@app.route('/json', methods=['POST'])
def post_json():
@ -56,6 +57,7 @@ class TestJSON(object):
def test_post_empty_json_wont_add_exception_to_response_if_no_debug(self, app, client):
app.config['DEBUG'] = False
app.config['TRAP_BAD_REQUEST_ERRORS'] = False
@app.route('/json', methods=['POST'])
def post_json():

32
tests/test_testing.py

@ -73,6 +73,38 @@ def test_environ_base_modified(app, client, app_ctx):
assert flask.g.user_agent == 'Bar'
def test_specify_url_scheme(app, client):
@app.route('/')
def index():
return flask.request.url
ctx = app.test_request_context(url_scheme='https')
assert ctx.request.url == 'https://localhost/'
rv = client.get('/', url_scheme='https')
assert rv.data == b'https://localhost/'
def test_blueprint_with_subdomain(app, client):
app.config['SERVER_NAME'] = 'example.com:1234'
app.config['APPLICATION_ROOT'] = '/foo'
bp = flask.Blueprint('company', __name__, subdomain='xxx')
@bp.route('/')
def index():
return flask.request.url
app.register_blueprint(bp)
ctx = app.test_request_context('/', subdomain='xxx')
assert ctx.request.url == 'http://xxx.example.com:1234/foo/'
assert ctx.request.blueprint == bp.name
rv = client.get('/', subdomain='xxx')
assert rv.data == b'http://xxx.example.com:1234/foo/'
def test_redirect_keep_session(app, client, app_ctx):
app.secret_key = 'testing'

17
tox.ini

@ -1,7 +1,7 @@
[tox]
envlist =
py{36,35,34,33,27,26,py}-release
py{36,27,py}-release-simplejson
py{36,35,34,33,27,26,py}
py{36,27,py}-simplejson
py{36,33,27,26,py}-devel
py{36,33,27,26,py}-lowest
docs-html
@ -35,12 +35,10 @@ commands =
pip install -e examples/patterns/largerapp -q
# pytest-cov doesn't seem to play nice with -p
coverage run -p -m pytest
coverage run -p -m pytest tests examples
[testenv:docs-html]
deps =
sphinx
flask-sphinx-themes
deps = sphinx
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
[testenv:docs-linkcheck]
@ -63,3 +61,10 @@ commands =
coverage combine
coverage report
codecov
[testenv:detox]
skip_install = true
deps = detox
commands =
detox -e py{36,35,34,33,27,26,py},py{36,27,py}-simplejson,py{36,33,27,26,py}-devel,py{36,33,27,26,py}-lowest,docs-html
tox -e coverage-report

Loading…
Cancel
Save