diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..3a7d2f63 --- /dev/null +++ b/.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$/ diff --git a/.gitattributes b/.gitattributes index 8383fff9..6f9ff673 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -CHANGES merge=union +CHANGES.rst merge=union diff --git a/.github/ISSUE_TEMPLATE.rst b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from .github/ISSUE_TEMPLATE.rst rename to .github/ISSUE_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.rst b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE.rst rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/.gitignore b/.gitignore index 231c0873..04b039bf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,19 +3,20 @@ .flaskenv *.pyc *.pyo -env +env/ env* -dist -build +dist/ +build/ *.egg -*.egg-info +*.egg-info/ _mailinglist -.tox +.tox/ .cache/ +.pytest_cache/ .idea/ # Coverage reports -htmlcov +htmlcov/ .coverage .coverage.* *,cover diff --git a/.travis.yml b/.travis.yml index 972ddb0c..0ff95ecd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,41 @@ +os: linux sudo: false language: python matrix: include: - python: 3.6 - env: TOXENV=py,codecov + env: TOXENV=py,simplejson,devel,lowest,codecov + - python: 3.6 + env: TOXENV=docs-html - python: 3.5 env: TOXENV=py,codecov - python: 3.4 env: TOXENV=py,codecov - - python: 3.3 - env: TOXENV=py,codecov - python: 2.7 - env: TOXENV=py,codecov - - python: 2.6 - env: TOXENV=py,codecov + env: TOXENV=py,simplejson,devel,lowest,codecov - python: pypy env: TOXENV=py,codecov - python: nightly env: TOXENV=py - - python: 3.6 - env: TOXENV=docs-html - - python: 3.6 - env: TOXENV=py-simplejson,codecov + - os: osx + language: generic + env: TOXENV=py + 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: - pip install tox diff --git a/AUTHORS b/AUTHORS index cbab2a8c..220046ed 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,39 +1,12 @@ -Flask is written and maintained by Armin Ronacher and -various contributors: +Flask is developed and maintained by the Pallets team and community +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 +A full list of contributors is available from git with:: -Patches and Suggestions -``````````````````````` - -- 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 + git shortlog -sne diff --git a/CHANGES b/CHANGES.rst similarity index 81% rename from CHANGES rename to CHANGES.rst index 357b289d..d33bac95 100644 --- a/CHANGES +++ b/CHANGES.rst @@ -1,121 +1,155 @@ +.. currentmodule:: flask + Flask Changelog =============== -Here you can see the full list of changes between each Flask release. -Version 0.13 ------------- +Version 1.0 +----------- -Major release, unreleased - -- 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. (`#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`_) -- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable - adding OPTIONS method when the ``view_func`` argument is not a class. - (`#1489`_). -- ``MethodView`` can inherit method handlers from base classes. (`#1936`_) -- Errors caused while opening the session at the beginning of the request are - handled by the app's error handlers. (`#2254`_) -- Blueprints gained ``json_encoder`` and ``json_decoder`` attributes to - override the app's encoder and decoder. (`#1898`_) -- ``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`_) +unreleased + +- **Python 2.6 and 3.3 are no longer supported.** (`pallets/meta#24`_) +- Bump minimum dependency versions to the latest stable versions: + Werkzeug >= 0.14, Jinja >= 2.10, itsdangerous >= 0.24, Click >= 5.1. + (`#2586`_) +- Skip :meth:`app.run ` when a Flask application is run from + the command line. This avoids some behavior that was confusing to + debug. +- Change the default for :data:`JSONIFY_PRETTYPRINT_REGULAR` to + ``False``. :func:`~json.jsonify` returns a compact format by default, + and an indented format in debug mode. (`#2193`_) +- :meth:`Flask.__init__ ` accepts the ``host_matching`` argument + and sets it on :attr:`~Flask.url_map`. (`#1559`_) +- :meth:`Flask.__init__ ` accepts the ``static_host`` argument + and passes it as the ``host`` argument when defining the static route. + (`#1559`_) +- :func:`send_file` supports Unicode in ``attachment_filename``. + (`#2223`_) +- Pass ``_scheme`` argument from :func:`url_for` to + :meth:`~Flask.handle_url_build_error`. (`#2017`_) +- :meth:`~Flask.add_url_rule` accepts the ``provide_automatic_options`` + argument to disable adding the ``OPTIONS`` method. (`#1489`_) +- :class:`~views.MethodView` subclasses inherit method handlers from + base classes. (`#1936`_) +- Errors caused while opening the session at the beginning of the + request are handled by the app's error handlers. (`#2254`_) +- Blueprints gained :attr:`~Blueprint.json_encoder` and + :attr:`~Blueprint.json_decoder` attributes to override the app's + encoder and decoder. (`#1898`_) +- :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 - address, as these may not behave properly in some browsers, such as Chrome. - (`#2282`_) + address, as these may not behave properly in some browsers, such as + Chrome. (`#2282`_) - Allow IP address as exact session cookie domain. (`#2282`_) -- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``. - (`#2282`_) -- Auto-detect zero-argument app factory called ``create_app`` or ``make_app`` - from ``FLASK_APP``. (`#2297`_) -- Factory functions are not required to take a ``script_info`` parameter to - 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`_) -- ``FLASK_APP`` can point to local packages that are not installed in dev mode, - although `pip install -e` should still be preferred. (`#2414`_) -- ``View.provide_automatic_options = True`` is set on the view function from - ``View.as_view``, to be detected in ``app.add_url_rule``. (`#2316`_) +- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through + ``SERVER_NAME``. (`#2282`_) +- Auto-detect zero-argument app factory called ``create_app`` or + ``make_app`` from ``FLASK_APP``. (`#2297`_) +- Factory functions are not required to take a ``script_info`` parameter + to work with the ``flask`` command. If they take a single parameter or + a parameter named ``script_info``, the :class:`~cli.ScriptInfo` object + will be passed. (`#2319`_) +- ``FLASK_APP`` can be set to an app factory, with arguments if needed, + for example ``FLASK_APP=myproject.app:create_app('dev')``. (`#2326`_) +- ``FLASK_APP`` can point to local packages that are not installed in + editable mode, although ``pip install -e`` is still preferred. + (`#2414`_) +- The :class:`~views.View` class attribute + :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``, - ``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`_) -- Allow registering new tags with ``TaggedJSONSerializer`` to support - storing other types in the session cookie. (`#2352`_) -- Only open the session if the request has not been pushed onto the context - stack yet. This allows ``stream_with_context`` generators to access the same - session that the containing view uses. (`#2354`_) -- Add ``json`` keyword argument for the test client request methods. This will - dump the given object as JSON and set the appropriate content type. - (`#2358`_) -- Extract JSON handling to a mixin applied to both the request and response - classes used by Flask. This adds the ``is_json`` and ``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 don't want to traverse the MRO. (`#2362`_) + ``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 not deleted). (`#2288`_) +- :meth:`~Flask.test_request_context` accepts ``subdomain`` and + ``url_scheme`` arguments for use when building the base URL. + (`#1621`_) +- Set :data:`APPLICATION_ROOT` to ``'/'`` by default. This was already + the implicit default when it was set to ``None``. +- :data:`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`_) +- Allow registering new tags with + :class:`~json.tag.TaggedJSONSerializer` to support storing other types + in the session cookie. (`#2352`_) +- Only open the session if the request has not been pushed onto the + context stack yet. This allows :func:`~stream_with_context` + generators to access the same session that the containing view uses. + (`#2354`_) +- Add ``json`` keyword argument for the test client request methods. + This will dump the given object as JSON and set the appropriate + content type. (`#2358`_) +- Extract JSON handling to a mixin applied to both the :class:`Request` + 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`_) -- Template auto reloading will honor the ``run`` command's ``debug`` flag even - if ``app.jinja_env`` was already accessed. (`#2373`_) +- Template auto reloading will honor debug mode even even if + :attr:`~Flask.jinja_env` was already accessed. (`#2373`_) - The following old deprecated code was removed. (`#2385`_) - - ``flask.ext`` - import extensions directly by their name instead of - through the ``flask.ext`` namespace. For example, - ``import flask.ext.sqlalchemy`` becomes ``import flask_sqlalchemy``. - - ``Flask.init_jinja_globals`` - extend ``Flask.create_jinja_environment`` - instead. - - ``Flask.error_handlers`` - tracked by ``Flask.error_handler_spec``, - use ``@app.errorhandler`` to register handlers. - - ``Flask.request_globals_class`` - use ``Flask.app_ctx_globals_class`` - instead. - - ``Flask.static_path`` - use ``Flask.static_url_path`` instead. - - ``Request.module`` - use ``Request.blueprint`` instead. - -- The ``request.json`` property is no longer deprecated. (`#1421`_) -- Support passing an existing ``EnvironBuilder`` or ``dict`` to - ``test_client.open``. (`#2412`_) -- The ``flask`` command and ``app.run`` will load environment variables using - from ``.env`` and ``.flaskenv`` files if python-dotenv is installed. - (`#2416`_) -- When passing a full URL to the test client, use the scheme in the URL instead - of the ``PREFERRED_URL_SCHEME``. (`#2430`_) -- ``app.logger`` has been simplified. ``LOGGER_NAME`` and - ``LOGGER_HANDLER_POLICY`` config was removed. The logger is always named - ``flask.app``. The level is only set on first access, it doesn't check - ``app.debug`` each time. Only one format is used, not different ones - depending on ``app.debug``. No handlers are removed, and a handler is only - added if no handlers are already configured. (`#2436`_) -- Blueprint view function name may not contain dots. (`#2450`_) - + - ``flask.ext`` - import extensions directly by their name instead of + through the ``flask.ext`` namespace. For example, + ``import flask.ext.sqlalchemy`` becomes ``import flask_sqlalchemy``. + - ``Flask.init_jinja_globals`` - extend + :meth:`Flask.create_jinja_environment` instead. + - ``Flask.error_handlers`` - tracked by + :attr:`Flask.error_handler_spec`, use :meth:`Flask.errorhandler` to + register handlers. + - ``Flask.request_globals_class`` - use + :attr:`Flask.app_ctx_globals_class` instead. + - ``Flask.static_path`` - use :attr:`Flask.static_url_path` instead. + - ``Request.module`` - use :attr:`Request.blueprint` instead. + +- The :attr:`Request.json` property is no longer deprecated. (`#1421`_) +- Support passing a :class:`~werkzeug.test.EnvironBuilder` or + ``dict`` to :meth:`test_client.open `. + (`#2412`_) +- The ``flask`` command and :meth:`Flask.run` will load environment + variables from ``.env`` and ``.flaskenv`` files if python-dotenv is + installed. (`#2416`_) +- When passing a full URL to the test client, the scheme in the URL is + used instead of :data:`PREFERRED_URL_SCHEME`. (`#2430`_) +- :attr:`Flask.logger` has been simplified. ``LOGGER_NAME`` and + ``LOGGER_HANDLER_POLICY`` config was removed. The logger is always + named ``flask.app``. The level is only set on first access, it doesn't + check :attr:`Flask.debug` each time. Only one format is used, not + 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 .. _#1489: https://github.com/pallets/flask/pull/1489 +.. _#1559: https://github.com/pallets/flask/issues/1559 .. _#1621: https://github.com/pallets/flask/pull/1621 .. _#1898: https://github.com/pallets/flask/pull/1898 .. _#1936: https://github.com/pallets/flask/pull/1936 @@ -146,6 +180,16 @@ Major release, unreleased .. _#2430: https://github.com/pallets/flask/pull/2430 .. _#2436: https://github.com/pallets/flask/pull/2436 .. _#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 -------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f6ff7015..a9bcace6 100644 --- a/CONTRIBUTING.rst +++ b/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. 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:: +combinations of Python and dependencies. You need to have Python 2.7, 3.4, +3.5 3.6, and PyPy 2.7 installed to run all of the environments. Then run:: tox @@ -131,8 +131,22 @@ Read more about `coverage `_. Running the full test suite with ``tox`` will combine the coverage reports 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 `_. + + +make targets +~~~~~~~~~~~~ Flask provides a ``Makefile`` with various shortcuts. They will ensure that all dependencies are installed. diff --git a/LICENSE b/LICENSE index a7da10e1..8f9252f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,33 +1,31 @@ -Copyright (c) 2015 by Armin Ronacher and contributors. See AUTHORS -for more details. +Copyright © 2010 by the Pallets team. Some rights reserved. -Redistribution and use in source and binary forms of the software as well -as documentation, with or without modification, are permitted provided -that the following conditions are met: +Redistribution and use in source and binary forms of the software as +well as documentation, with or without modification, are permitted +provided that the following conditions are met: -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT -NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in index f8d9c2ae..d8a725b5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include Makefile CHANGES LICENSE AUTHORS +include Makefile CHANGES.rst LICENSE AUTHORS tox.ini graft artwork graft tests diff --git a/Makefile b/Makefile index aef8a782..7df5fd00 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,6 @@ test: clean-pyc install-dev pytest coverage: clean-pyc install-dev - pip install -q -e .[test] coverage run -m pytest coverage report coverage html @@ -34,20 +33,3 @@ clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -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' diff --git a/README b/README deleted file mode 100644 index 75c5e7b1..00000000 --- a/README +++ /dev/null @@ -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. - - diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..6f6dc581 --- /dev/null +++ b/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/ diff --git a/docs/_static/pycharm-runconfig.png b/docs/_static/pycharm-runconfig.png index 0fa0810f..dff21fa0 100644 Binary files a/docs/_static/pycharm-runconfig.png and b/docs/_static/pycharm-runconfig.png differ diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst index 82b3dc58..bd56f53c 100644 --- a/docs/advanced_foreword.rst +++ b/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 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`. diff --git a/docs/api.rst b/docs/api.rst index e24160c4..0db2f6b6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -188,6 +188,15 @@ Test Client :members: +Test CLI Runner +--------------- + +.. currentmodule:: flask.testing + +.. autoclass:: FlaskCliRunner + :members: + + Application Globals ------------------- @@ -361,22 +370,6 @@ Configuration .. autoclass:: Config :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 -------------- diff --git a/docs/changelog.rst b/docs/changelog.rst index d6c5f48c..d9e113ec 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1 +1 @@ -.. include:: ../CHANGES +.. include:: ../CHANGES.rst diff --git a/docs/cli.rst b/docs/cli.rst index cdb0fc59..456fdc03 100644 --- a/docs/cli.rst +++ b/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. -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 -application's :attr:`~Flask.debug` flag. The value ``1`` enables it, ``0`` -disables it. Forcing the debug flag on also enables the debugger and reloader -when running the development server. :: +If the env is set to ``development``, the ``flask`` command will enable +debug mode and ``flask run`` will enable the interactive debugger and +reloader. - $ FLASK_DEBUG=1 flask run +:: + + $ FLASK_ENV=development flask run * 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) * Restarting with inotify reloader * Debugger is active! * 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: Environment Variables From dotenv @@ -176,7 +194,7 @@ Unix Bash, :file:`venv/bin/activate`:: export FLASK_APP=hello -Windows CMD, :file:`venv\Scripts\activate.bat`:: +Windows CMD, :file:`venv\\Scripts\\activate.bat`:: set FLASK_APP=hello @@ -229,6 +247,9 @@ group. This is useful if you want to organize multiple related commands. :: flask user create demo +See :ref:`testing-cli` for an overview of how to test your custom +commands. + Application Context ~~~~~~~~~~~~~~~~~~~ @@ -337,47 +358,67 @@ script is available. Note that you don't need to set ``FLASK_APP``. :: $ pip install -e . $ 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 PyCharm Integration ------------------- -The new Flask CLI features aren't yet fully integrated into the PyCharm IDE, -so we have to do a few tweaks to get them working smoothly. These instructions -should be similar for any other IDE you might want to use. +Prior to PyCharm 2018.1, the Flask CLI features weren't yet fully +integrated into PyCharm. We have to do a few tweaks to get them working +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 -*Edit Configurations*. You'll be greeted by a screen similar to this: +In PyCharm, with your project open, click on *Run* from the menu bar and +go to *Edit Configurations*. You'll be greeted by a screen similar to +this: .. image:: _static/pycharm-runconfig.png - :align: center - :class: screenshot - :alt: screenshot of pycharm's run configuration settings + :align: center + :class: screenshot + :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, we can easily copy the entire configuration and make a single +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. -There's quite a few options to change, but once we've done it for one command, -we can easily copy the entire configuration and make a single tweak to give us -access to other commands, including any custom ones you may implement yourself. +Select *Module name* from the dropdown (**A**) then input ``flask``. -For the *Script* input (**A**), navigate to your project's virtual environment. -Within that folder, pick the ``flask`` executable which will reside in the -``bin`` folder, or in the ``Scripts`` on Windows. +The *Parameters* field (**B**) is set to the CLI command to execute +(with any arguments). In this example we use ``run``, which will run +the development server. -The *Script Parameter* field (**B**) is set to the CLI command you to execute. -In this example we use ``run``, which will run the development server. +You can skip this next step if you're using :ref:`dotenv`. We need to +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 -environment variable (**C**) to identify our application. Click on the browse -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 folder where +our application resides. -Next we need to set the working directory (**D**) to be the same folder where -our application file or package resides. PyCharm changed it to the directory -with the ``flask`` executable when we selected it earlier, which is incorrect. +If you have installed your project as a package in your virtualenv, you +may untick the *PYTHONPATH* options (**E**). This will more accurately +match how you deploy the app later. -Finally, untick the *PYTHONPATH* options (**E**) and give the configuration a -good descriptive name, such as "Run Flask Server", and click *Apply*. +Click *Apply* to save the configuration, or *OK* to save and close the +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, -we can simply copy that configuration and alter the *Script* argument +Now that we have a configuration which runs ``flask run`` from within +PyCharm, we can copy that configuration and alter the *Script* argument to run a different CLI command, e.g. ``flask shell``. diff --git a/docs/conf.py b/docs/conf.py index 94cae16d..682391f3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,8 @@ import pkg_resources import time import datetime +from sphinx.application import Sphinx + 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, @@ -296,3 +298,17 @@ def unwrap_decorators(): 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) diff --git a/docs/config.rst b/docs/config.rst index 6e30466e..c496ac00 100644 --- a/docs/config.rst +++ b/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 this is also where you can have your own configuration. + 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:: app = Flask(__name__) - app.config['DEBUG'] = True + app.config['TESTING'] = True Certain configuration values are also forwarded to the :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` method:: app.config.update( - DEBUG=True, + TESTING=True, 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 - server, to enable the debug mode, you need to export the ``FLASK_DEBUG`` - environment variable before running the server:: +Environment and Debug Features +------------------------------ + +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 - (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 ---------------------------- 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 - 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. + Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for + 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 @@ -104,7 +147,7 @@ The following configuration values are used internally by Flask: 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`` 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 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. + set, the cookie will be valid for all subdomains of :data:`SERVER_NAME`. + If ``False``, the cookie's domain will not be set. Default: ``None`` @@ -165,6 +208,16 @@ The following configuration values are used internally by Flask: 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 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 - Inform the application what host and port it is bound to. Required for - subdomain route matching support. + 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. :: + :data:`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 @@ -318,10 +372,16 @@ The following configuration values are used internally by Flask: ``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING`` .. versionchanged:: 1.0 - ``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See :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 ---------------------- @@ -588,4 +648,3 @@ Example usage for both:: # or via open_instance_resource: with app.open_instance_resource('application.cfg') as f: config = f.read() - diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index de4d7a91..f76b1591 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -56,7 +56,6 @@ Design notes, legal information and changelog are here for the interested. unicode extensiondev styleguide - python3 upgrading changelog license diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index 5ca2a084..46706033 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -111,7 +111,7 @@ Set yourapplication.fcgi:: #!/usr/bin/python #: optional path to your local python site-packages folder import sys - sys.path.insert(0, '/lib/python2.6/site-packages') + sys.path.insert(0, '/lib/python/site-packages') from flup.server.fcgi import WSGIServer from yourapplication import app diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 6f014bff..edf5a256 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -4,9 +4,8 @@ Deployment Options ================== 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 -request at a time. Some of the options available for properly running Flask in -production are documented here. +for production** as it doesn't scale well. Some of the options available for +properly running Flask in production are documented 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 diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index bf680976..5b0740a6 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/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 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 Web: https://twistedmatrix.com/trac/wiki/TwistedWeb diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 413680dd..4c260112 100644 --- a/docs/errorhandling.rst +++ b/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 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:: diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 3e8f30d4..677cc19a 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -159,19 +159,10 @@ The Extension Code Here's the contents of the `flask_sqlite3.py` for copy/paste:: import sqlite3 - from flask import current_app - - # 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 + from flask import current_app, _app_ctx_stack class SQLite3(object): - def __init__(self, app=None): self.app = app 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): 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) - else: - app.teardown_request(self.teardown) + app.teardown_appcontext(self.teardown) def connect(self): return sqlite3.connect(current_app.config['SQLITE3_DATABASE']) def teardown(self, exception): - ctx = stack.top + ctx = _app_ctx_stack.top if hasattr(ctx, 'sqlite3_db'): ctx.sqlite3_db.close() @property def connection(self): - ctx = stack.top + ctx = _app_ctx_stack.top if ctx is not None: if not hasattr(ctx, 'sqlite3_db'): 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 configuration for the database, defaulting to an in memory database if no configuration is supplied. In addition, the ``init_app`` method attaches - the ``teardown`` handler. It will try to use the newstyle app context - handler and if it does not exist, falls back to the request context - one. + the ``teardown`` handler. 3. Next, we define a ``connect`` method that opens a database connection. 4. Finally, we add a ``connection`` property that on first access opens 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 application context via ``_app_ctx_stack.top``. Extensions should use the top context for storing their own information with a sufficiently - complex name. Note that we're falling back to the - ``_request_ctx_stack.top`` if the application is using an older - version of Flask that does not support it. + complex name. So why did we decide on a class-based approach here? Because using our extension looks something like this:: @@ -245,9 +227,8 @@ You can then use the database from views like this:: cur = db.connection.cursor() cur.execute(...) -Likewise if you are outside of a request but you are using Flask 0.9 or -later with the app context support, you can use the database in the same -way:: +Likewise if you are outside of a request you can use the database by +pushing an app context:: with app.app_context(): 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 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 ----------------- @@ -383,26 +336,7 @@ extension to be approved you have to follow these guidelines: (``PackageName==dev``). 9. The ``zip_safe`` flag in the setup script must be set to ``False``, even if the extension would be safe for zipping. -10. An extension currently has to support Python 2.7, Python 3.3 and higher. - - - -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``. +10. An extension currently has to support Python 3.4 and newer and 2.7. .. _OAuth extension: https://pythonhosted.org/Flask-OAuth/ diff --git a/docs/extensions.rst b/docs/extensions.rst index 6deb9652..54e2c3eb 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -1,58 +1,53 @@ .. _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 ------------------ -Flask extensions are listed on the `Flask Extension Registry`_ and can be -downloaded with :command:`easy_install` or :command:`pip`. If you add a Flask extension -as dependency to your :file:`requirements.txt` or :file:`setup.py` file they are -usually installed with a simple command or when your application installs. +Flask extensions are usually named "Flask-Foo" or "Foo-Flask". Many +extensions are listed in the `Extension Registry`_, which can be updated +by extension developers. You can also search PyPI for packages tagged +with `Framework :: Flask `_. + Using Extensions ---------------- -Extensions typically have documentation that goes along that shows how to -use it. There are no general rules in how extensions are supposed to -behave but they are imported from common locations. If you have an -extension called ``Flask-Foo`` or ``Foo-Flask`` it should be always -importable from ``flask_foo``:: +Consult each extension's documentation for installation, configuration, +and usage instructions. Generally, extensions pull their own +configuration from :attr:`app.config ` and are +passed an application instance during initialization. For example, +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 -an extension that fits your need. If this is the case, you can always create your own. -Consider reading :ref:`extension-dev` to develop your own Flask extension. + app = Flask(__name__) + app.config.update( + 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:: - - import flaskext_compat - flaskext_compat.activate() - - from flask.ext import foo +Building Extensions +------------------- -Once the ``flaskext_compat`` module is activated the :data:`flask.ext` will -exist and you can start importing from there. +While the `Extension Registry`_ contains many Flask extensions, you may +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/ -.. _flaskext_compat.py: https://raw.githubusercontent.com/pallets/flask/master/scripts/flaskext_compat.py +.. _Extension Registry: http://flask.pocoo.org/extensions/ +.. _pypi: https://pypi.python.org/pypi?:action=browse&c=585 diff --git a/docs/index.rst b/docs/index.rst index 617104ee..5e9f8426 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,28 +4,23 @@ Welcome to Flask ================ .. image:: _static/logo-full.png - :alt: Flask: web development, one drop at a time - :class: floatingflask + :alt: Flask: web development, one drop at a time + :class: floatingflask -Welcome to Flask's documentation. This documentation is divided into -different parts. I recommend that you get started with -:ref:`installation` and then head over to the :ref:`quickstart`. -Besides the quickstart, there is also a more detailed :ref:`tutorial` that -shows how to create a complete (albeit small) application with Flask. If -you'd rather dive into the internals of Flask, check out -the :ref:`api` documentation. Common patterns are described in the -:ref:`patterns` section. +Welcome to Flask's documentation. Get started with :ref:`installation` +and then get an overview with the :ref:`quickstart`. There is also a +more detailed :ref:`tutorial` that shows how to create a small but +complete application with Flask. Common patterns are described in the +:ref:`patterns` section. The rest of the docs desribe each component of +Flask in detail, with a full reference in the :ref:`api` section. -Flask depends on two external libraries: the `Jinja2`_ template -engine and the `Werkzeug`_ WSGI toolkit. These libraries are not documented -here. If you want to dive into their documentation, check out the -following links: +Flask depends on the `Jinja`_ template engine and the `Werkzeug`_ WSGI +toolkit. The documentation for these libraries can be found at: -- `Jinja2 Documentation `_ -- `Werkzeug Documentation `_ +- `Jinja documentation `_ +- `Werkzeug documentation `_ - -.. _Jinja2: http://jinja.pocoo.org/ -.. _Werkzeug: http://werkzeug.pocoo.org/ +.. _Jinja: https://www.palletsprojects.com/p/jinja/ +.. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/ .. include:: contents.rst.inc diff --git a/docs/installation.rst b/docs/installation.rst index 370ce48c..88b9af09 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,8 +6,8 @@ Installation Python Version -------------- -We recommend using the latest version of Python 3. Flask supports Python 3.3 -and newer, Python 2.6 and newer, and PyPy. +We recommend using the latest version of Python 3. Flask supports Python 3.4 +and newer, Python 2.7, and PyPy. Dependencies ------------ diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index b6d4a729..3e880205 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -89,17 +89,19 @@ For more information about the design of extensions refer to :doc:`/extensiondev Using Applications ------------------ -To use such an application you have to create it in a separate file first, -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:: +To run such an application, you can use the :command:`flask` command:: - from yourapplication import create_app - app = create_app('/path/to/config.cfg') - -It can then be used with the :command:`flask` command:: + export FLASK_APP=myapp + flask run + +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 + +Then the ``create_app`` factory in ``myapp`` is called with the string +``'dev'`` as the argument. See :doc:`/cli` for more detail. Factory Improvements -------------------- diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index d0215c58..c7ae14f9 100644 --- a/docs/patterns/fileuploads.rst +++ b/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): filename = secure_filename(file.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)) return ''' diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index cc149839..6b0ee7ad 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -65,10 +65,10 @@ that tells Flask where to find the application instance:: export FLASK_APP=yourapplication If you are outside of the project directory make sure to provide the exact -path to your application directory. Similarly you can turn on "debug -mode" with this environment variable:: +path to your application directory. Similarly you can turn on the +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 commands:: diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 15f38ea7..eecaaae8 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -67,7 +67,7 @@ the application context by hand:: 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 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 diff --git a/docs/python3.rst b/docs/python3.rst deleted file mode 100644 index a7a4f165..00000000 --- a/docs/python3.rst +++ /dev/null @@ -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. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 7440ccd2..50db1dff 100644 --- a/docs/quickstart.rst +++ b/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 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:: - $ export FLASK_DEBUG=1 + $ export FLASK_ENV=development $ 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: @@ -144,6 +145,9 @@ This does the following things: 2. it activates the automatic reloader 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. .. admonition:: Attention @@ -224,7 +228,7 @@ Converter types: Unique URLs / Redirection Behavior `````````````````````````````````` -Take these two rules:: +The following two rules differ in their use of a trailing slash. :: @app.route('/projects/') def projects(): @@ -234,20 +238,17 @@ Take these two rules:: def about(): return 'The about page' -Though they look similar, they differ in their use of the trailing slash in -the URL. In the first case, the canonical URL for the ``projects`` endpoint -uses a trailing slash. It's similar to a folder in a file system; if you -access the URL without a trailing slash, Flask redirects you to the -canonical URL with the trailing slash. +The canonical URL for the ``projects`` endpoint has a trailing slash. +It's similar to a folder in a file system. If you access the URL without +a trailing slash, Flask redirects you to the canonical URL with the +trailing slash. -In the second case, however, the URL definition lacks a trailing slash, -like the pathname of a file on UNIX-like systems. Accessing the URL with a -trailing slash produces a 404 “Not Found” error. +The canonical URL for the ``about`` endpoint does not have a trailing +slash. It's similar to the pathname of a file. Accessing the URL with a +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: @@ -267,7 +268,9 @@ Why would you want to build URLs using the URL reversing function manually change hard-coded URLs. 3. URL building handles escaping of special characters and Unicode data 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 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']) def login(): if request.method == 'POST': - do_the_login() + return do_the_login() else: - show_the_login_form() + return show_the_login_form() If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise, diff --git a/docs/security.rst b/docs/security.rst index 13ea2e33..44c095ac 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -184,6 +184,9 @@ contains the same data. :: - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection + +.. _security-cookie: + Set-Cookie options ~~~~~~~~~~~~~~~~~~ @@ -194,17 +197,21 @@ 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. +- ``SameSite`` restricts how cookies are sent with requests from + 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( SESSION_COOKIE_SECURE=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 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/Headers/Set-Cookie +.. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute + + HTTP Public Key Pinning (HPKP) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/server.rst b/docs/server.rst index f8332ebf..db431a6c 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -12,23 +12,33 @@ but you can also continue using the :meth:`Flask.run` method. Command Line ------------ -The :command:`flask` command line script (:ref:`cli`) is strongly recommended for -development because it provides a superior reload experience due to how it -loads the application. The basic usage is like this:: +The :command:`flask` command line script (:ref:`cli`) is strongly +recommended for development because it provides a superior reload +experience due to how it loads the application. The basic usage is like +this:: $ export FLASK_APP=my_application - $ export FLASK_DEBUG=1 + $ export FLASK_ENV=development $ 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/*. The individual features of the server can be controlled by passing more -arguments to the ``run`` option. For instance the reloader can be +arguments to the ``run`` option. For instance the reloader can be disabled:: $ 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 ------- diff --git a/docs/testing.rst b/docs/testing.rst index a040b7ef..4a272df6 100644 --- a/docs/testing.rst +++ b/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 ``application/json``. You can get the JSON data from the request or response 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 diff --git a/docs/tutorial/packaging.rst b/docs/tutorial/packaging.rst index 5db921aa..e08f26fa 100644 --- a/docs/tutorial/packaging.rst +++ b/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/``:: export FLASK_APP=flaskr - export FLASK_DEBUG=true + export FLASK_ENV=development flask run (In case you are on Windows you need to use ``set`` instead of ``export``). -The :envvar:`FLASK_DEBUG` flag enables or disables the interactive debugger. +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 allow users to execute code on the server! diff --git a/examples/blueprintexample/blueprintexample.py b/examples/blueprintexample/blueprintexample.py index 78ee3a5b..6ca0dd13 100644 --- a/examples/blueprintexample/blueprintexample.py +++ b/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 simple_page.simple_page import simple_page diff --git a/examples/blueprintexample/test_blueprintexample.py b/examples/blueprintexample/test_blueprintexample.py index 2f3dd93f..44df7762 100644 --- a/examples/blueprintexample/test_blueprintexample.py +++ b/examples/blueprintexample/test_blueprintexample.py @@ -1,10 +1,12 @@ # -*- 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 blueprintexample diff --git a/examples/flaskr/flaskr/blueprints/flaskr.py b/examples/flaskr/flaskr/blueprints/flaskr.py index 7b64dd9e..e42bee62 100644 --- a/examples/flaskr/flaskr/blueprints/flaskr.py +++ b/examples/flaskr/flaskr/blueprints/flaskr.py @@ -6,7 +6,7 @@ A microblog example application written as Flask tutorial with Flask and sqlite3. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/examples/flaskr/flaskr/factory.py b/examples/flaskr/flaskr/factory.py index 7541ec3c..b504f64a 100644 --- a/examples/flaskr/flaskr/factory.py +++ b/examples/flaskr/flaskr/factory.py @@ -6,7 +6,7 @@ A microblog example application written as Flask tutorial with Flask and sqlite3. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/examples/flaskr/setup.py b/examples/flaskr/setup.py index 7f1dae53..f8995a07 100644 --- a/examples/flaskr/setup.py +++ b/examples/flaskr/setup.py @@ -5,7 +5,7 @@ Tests the Flaskr application. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/examples/flaskr/tests/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py index b5ade2ec..6e7618d5 100644 --- a/examples/flaskr/tests/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -5,7 +5,7 @@ Tests the Flaskr application. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ @@ -17,33 +17,25 @@ from flaskr.blueprints.flaskr import init_db @pytest.fixture -def app(request): - - db_fd, temp_db_location = tempfile.mkstemp() +def app(): + db_fd, db_path = tempfile.mkstemp() config = { - 'DATABASE': temp_db_location, + 'DATABASE': db_path, 'TESTING': True, - 'DB_FD': db_fd } - app = create_app(config=config) with app.app_context(): init_db() 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): diff --git a/examples/jqueryexample/jqueryexample.py b/examples/jqueryexample/jqueryexample.py index 39b81951..561e5375 100644 --- a/examples/jqueryexample/jqueryexample.py +++ b/examples/jqueryexample/jqueryexample.py @@ -5,9 +5,10 @@ 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. """ + from flask import Flask, jsonify, render_template, request app = Flask(__name__) diff --git a/examples/minitwit/minitwit/minitwit.py b/examples/minitwit/minitwit/minitwit.py index 50693dd8..2fe002e2 100644 --- a/examples/minitwit/minitwit/minitwit.py +++ b/examples/minitwit/minitwit/minitwit.py @@ -5,7 +5,7 @@ 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. """ diff --git a/examples/minitwit/tests/test_minitwit.py b/examples/minitwit/tests/test_minitwit.py index c8992e57..3decc6da 100644 --- a/examples/minitwit/tests/test_minitwit.py +++ b/examples/minitwit/tests/test_minitwit.py @@ -5,9 +5,10 @@ Tests the MiniTwit application. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ + import os import tempfile import pytest diff --git a/examples/patterns/largerapp/tests/test_largerapp.py b/examples/patterns/largerapp/tests/test_largerapp.py index 6bc0531e..32553d7c 100644 --- a/examples/patterns/largerapp/tests/test_largerapp.py +++ b/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 import pytest @@ -9,4 +18,4 @@ def client(): def test_index(client): rv = client.get('/') - assert b"Hello World!" in rv.data \ No newline at end of file + assert b"Hello World!" in rv.data diff --git a/examples/patterns/largerapp/yourapplication/__init__.py b/examples/patterns/largerapp/yourapplication/__init__.py index 09407711..c2e05dda 100644 --- a/examples/patterns/largerapp/yourapplication/__init__.py +++ b/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 app = Flask('yourapplication') diff --git a/examples/patterns/largerapp/yourapplication/views.py b/examples/patterns/largerapp/yourapplication/views.py index b112328e..5337eab7 100644 --- a/examples/patterns/largerapp/yourapplication/views.py +++ b/examples/patterns/largerapp/yourapplication/views.py @@ -1,5 +1,14 @@ +# -*- coding: utf-8 -*- +""" +yourapplication.views +~~~~~~~~~~~~~~~~~~~~~ + +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. +""" + from yourapplication import app @app.route('/') def index(): - return 'Hello World!' \ No newline at end of file + return 'Hello World!' diff --git a/flask/__init__.py b/flask/__init__.py index bb6c4c18..08fb1dad 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -6,7 +6,7 @@ A microframework based on Werkzeug. It's extensively documented and follows best practice patterns. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/flask/__main__.py b/flask/__main__.py index cbefccd2..4aee6543 100644 --- a/flask/__main__.py +++ b/flask/__main__.py @@ -5,11 +5,10 @@ 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. """ - if __name__ == '__main__': from .cli import main main(as_module=True) diff --git a/flask/_compat.py b/flask/_compat.py index 173b3689..a3b5b9c1 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -7,9 +7,10 @@ version of six so we don't have to depend on a specific version of it. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ + import sys PY2 = sys.version_info[0] == 2 diff --git a/flask/app.py b/flask/app.py index 88ca433c..89be4dc5 100644 --- a/flask/app.py +++ b/flask/app.py @@ -5,9 +5,10 @@ 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. """ + import os import sys import warnings @@ -27,7 +28,7 @@ from .config import Config, ConfigAttribute from .ctx import AppContext, RequestContext, _AppCtxGlobals from .globals import _request_ctx_stack, g, request, session 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 from .logging import create_logger from .sessions import SecureCookieSessionInterface @@ -122,8 +123,13 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.11 The `root_path` parameter was added. - .. versionadded:: 0.13 - The `host_matching` and `static_host` parameters were added. + .. versionadded:: 1.0 + 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 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 at `static_url_path`. Defaults to the ``'static'`` folder in the root path of the application. - :param host_matching: sets the app's ``url_map.host_matching`` to the given - value. Defaults to False. - :param static_host: the host to use when adding the static route. Defaults - to None. Required when using ``host_matching=True`` - with a ``static_folder`` configured. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param 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 be used by the application. Defaults to ``'templates'`` folder in the root path of the @@ -196,15 +204,6 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.11 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 #: Flask extensions (and in the future probably also Flask itself). #: For example this might activate test helpers that have an @@ -278,7 +277,8 @@ class Flask(_PackageBoundObject): #: Default configuration parameters. default_config = ImmutableDict({ - 'DEBUG': get_debug_flag(default=False), + 'ENV': None, + 'DEBUG': None, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, @@ -292,6 +292,7 @@ class Flask(_PackageBoundObject): 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, + 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), @@ -317,6 +318,14 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.7 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 #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. #: @@ -345,6 +354,7 @@ class Flask(_PackageBoundObject): static_folder='static', static_host=None, host_matching=False, + subdomain_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, @@ -528,6 +538,7 @@ class Flask(_PackageBoundObject): self.url_map = Map() self.url_map.host_matching = host_matching + self.subdomain_matching = subdomain_matching # tracks internally if the application already handled at least one # request. @@ -647,7 +658,10 @@ class Flask(_PackageBoundObject): root_path = self.root_path if instance_relative: 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): """Tries to locate the instance path if it was not provided to the @@ -790,25 +804,41 @@ class Flask(_PackageBoundObject): rv.update(processor()) return rv - def _reconfigure_for_run_debug(self, debug): - """The ``run`` commands will set the application's debug flag. Some - application configuration may already be calculated based on the - previous debug value. This method will recalculate affected values. - - Called by the :func:`flask.cli.run` command or :meth:`Flask.run` - method if the debug flag is set explicitly in the call. + #: What environment the app is running in. Flask and extensions may + #: enable behaviors based on the environment, such as enabling debug + #: mode. This maps to the :data:`ENV` 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'`` + env = ConfigAttribute('ENV') - :param debug: the new value of the debug flag + def _get_debug(self): + return self.config['DEBUG'] - .. versionadded:: 1.0 - Reconfigures ``app.jinja_env.auto_reload``. - """ - self.debug = debug + def _set_debug(self, value): + self.config['DEBUG'] = value self.jinja_env.auto_reload = self.templates_auto_reload - def run( - self, host=None, port=None, debug=None, load_dotenv=True, **options - ): + #: Whether debug mode is enabled. When using ``flask run`` to start + #: 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. Do not use ``run()`` in a production setting. It is not intended to @@ -856,27 +886,40 @@ class Flask(_PackageBoundObject): If installed, python-dotenv will be used to load environment variables from :file:`.env` and :file:`.flaskenv` files. - .. versionchanged:: 0.10 - The default port is now picked from the ``SERVER_NAME`` variable. + If set, the :envvar:`FLASK_ENV` and :envvar:`FLASK_DEBUG` + 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 - # command line. Have a look at cli.py for more information. + # command line. Have a look at cli.py for more information. if os.environ.get('FLASK_RUN_FROM_CLI') == 'true': from .debughelpers import explain_ignored_app_run explain_ignored_app_run() return if load_dotenv: - from flask.cli import load_dotenv - load_dotenv() + cli.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: - self._reconfigure_for_run_debug(bool(debug)) + self.debug = bool(debug) _host = '127.0.0.1' _port = 5000 - server_name = self.config.get("SERVER_NAME") + server_name = self.config.get('SERVER_NAME') sn_host, sn_port = None, None if server_name: @@ -884,8 +927,12 @@ class Flask(_PackageBoundObject): host = host or sn_host or _host port = int(port or sn_port or _port) + options.setdefault('use_reloader', 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 @@ -953,6 +1000,23 @@ class Flask(_PackageBoundObject): from flask.testing import FlaskClient as cls 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): """Creates or opens a new session. Default implementation stores all session data in a signed cookie. This requires that the @@ -1055,7 +1119,8 @@ class Flask(_PackageBoundObject): return iter(self._blueprint_order) @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` decorator. If a view_func is provided it will be registered with the endpoint. @@ -1921,19 +1986,30 @@ class Flask(_PackageBoundObject): return rv def create_url_adapter(self, request): - """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 - so the request is passed explicitly. + """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 so the request is passed explicitly. .. versionadded:: 0.6 .. versionchanged:: 0.9 This can now also be called without a request object when the 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: - return self.url_map.bind_to_environ(request.environ, - server_name=self.config['SERVER_NAME']) + # If subdomain matching is disabled (the default), use the + # 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 # to work. if self.config['SERVER_NAME'] is not None: @@ -2109,8 +2185,8 @@ class Flask(_PackageBoundObject): return RequestContext(self, environ) def test_request_context(self, *args, **kwargs): - """Creates a WSGI environment from the given values (see - :class:`werkzeug.test.EnvironBuilder` for more information, this + """Creates a :class:`~flask.ctx.RequestContext` from the given values + (see :class:`werkzeug.test.EnvironBuilder` for more information, this function accepts the same arguments plus two additional). Additional arguments (only if ``base_url`` is not specified): diff --git a/flask/blueprints.py b/flask/blueprints.py index 4c9938e2..d633685f 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -6,7 +6,7 @@ Blueprints are the recommended way to implement larger or more 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. """ from functools import update_wrapper @@ -52,6 +52,9 @@ class BlueprintSetupState(object): #: The prefix that should be used for all URLs defined on the #: blueprint. + if url_prefix and url_prefix[-1] == '/': + url_prefix = url_prefix[:-1] + self.url_prefix = url_prefix #: A dictionary with URL defaults that is added to each and every diff --git a/flask/cli.py b/flask/cli.py index 8bfb21f6..2a65498e 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -5,15 +5,17 @@ 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. """ + from __future__ import print_function import ast import inspect import os import re +import ssl import sys import traceback from functools import update_wrapper @@ -21,11 +23,12 @@ from operator import attrgetter from threading import Lock, Thread import click +from werkzeug.utils import import_string from . import __version__ -from ._compat import getargspec, iteritems, reraise +from ._compat import getargspec, iteritems, reraise, text_type from .globals import current_app -from .helpers import get_debug_flag +from .helpers import get_debug_flag, get_env try: import dotenv @@ -75,6 +78,8 @@ def find_best_app(script_info, module): if isinstance(app, Flask): return app except TypeError: + if not _called_with_wrong_args(app_factory): + raise raise NoAppException( 'Detected factory "{factory}" in module "{module}", but ' 'could not call it without arguments. Use ' @@ -111,6 +116,30 @@ def call_factory(script_info, app_factory, arguments=()): 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): """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 @@ -148,6 +177,9 @@ def find_app_by_string(script_info, module, app_name): try: app = call_factory(script_info, attr, args) except TypeError as e: + if not _called_with_wrong_args(attr): + raise + raise NoAppException( '{e}\nThe factory "{app_name}" in module "{module}" could not ' 'be called with the specified arguments.'.format( @@ -341,9 +373,8 @@ class ScriptInfo(object): else: for path in ('wsgi.py', 'app.py'): import_name = prepare_import(path) - app = locate_app( - self, import_name, None, raise_if_not_found=False - ) + app = locate_app(self, import_name, None, + raise_if_not_found=False) if app: break @@ -357,8 +388,10 @@ class ScriptInfo(object): 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: - app._reconfigure_for_run_debug(debug) + app.debug = debug self._loaded_app = app return app @@ -432,10 +465,8 @@ class FlaskGroup(AppGroup): from :file:`.env` and :file:`.flaskenv` files. """ - def __init__( - self, add_default_commands=True, create_app=None, - add_version_option=True, load_dotenv=True, **extra - ): + def __init__(self, add_default_commands=True, create_app=None, + add_version_option=True, load_dotenv=True, **extra): params = list(extra.pop('params', None) or ()) if add_version_option: @@ -578,62 +609,157 @@ def load_dotenv(path=None): 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.option('--host', '-h', default='127.0.0.1', help='The interface to bind to.') @click.option('--port', '-p', default=5000, 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, - 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.') @click.option('--debugger/--no-debugger', default=None, - help='Enable or disable the debugger. By default the debugger ' + help='Enable or disable the debugger. By default the debugger ' 'is active if debug is enabled.') @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.') -@click.option('--with-threads/--without-threads', default=False, +@click.option('--with-threads/--without-threads', default=True, help='Enable or disable multithreading.') @pass_script_info def run_command(info, host, port, reload, debugger, eager_loading, - with_threads): - """Runs a local development server for the Flask application. + with_threads, cert): + """Run a local development server. - This local server is recommended for development purposes only but it - can also be used for simple intranet deployments. By default it will - 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. + This server is for development purposes only. It does not provide + the stability, security, or performance of production WSGI servers. - The reloader and debugger are by default enabled if the debug flag of - Flask is enabled and disabled otherwise. + The reloader and debugger are enabled by default if + FLASK_ENV=development or FLASK_DEBUG=1. """ - from werkzeug.serving import run_simple - debug = get_debug_flag() + if reload is None: - reload = bool(debug) + reload = debug + if debugger is None: - debugger = bool(debug) + debugger = debug + if eager_loading is None: eager_loading = not reload + show_server_banner(get_env(), debug, info.app_import_path) app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) - # Extra startup messages. This depends a bit on Werkzeug internals to - # not double execute when the reloader kicks in. - if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': - # If we have an import path we can print it out now which can help - # 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) + from werkzeug.serving import run_simple + run_simple(host, port, app, use_reloader=reload, use_debugger=debugger, + threaded=with_threads, ssl_context=cert) @click.command('shell', short_help='Runs a shell in the app context.') @@ -649,11 +775,11 @@ def shell_command(): import code from flask.globals import _app_ctx_stack 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.platform, app.import_name, - app.debug and ' [debug]' or '', + app.env, app.instance_path, ) ctx = {} @@ -722,12 +848,12 @@ A general utility script for Flask applications. Provides commands from Flask, extensions, and the application. Loads the application defined in the FLASK_APP environment variable, or from a wsgi.py -file. Debug mode can be controlled with the FLASK_DEBUG -environment variable. +file. Setting the FLASK_ENV environment variable to 'development' will enable +debug mode. \b {prefix}{cmd} FLASK_APP=hello.py - {prefix}{cmd} FLASK_DEBUG=1 + {prefix}{cmd} FLASK_ENV=development {prefix}flask run """.format( cmd='export' if os.name == 'posix' else 'set', diff --git a/flask/config.py b/flask/config.py index 697add71..d6074baa 100644 --- a/flask/config.py +++ b/flask/config.py @@ -5,7 +5,7 @@ Implements the configuration related objects. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ @@ -129,7 +129,9 @@ class Config(dict): with open(filename, mode='rb') as config_file: exec(compile(config_file.read(), filename, 'exec'), d.__dict__) 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 e.strerror = 'Unable to load configuration file (%s)' % e.strerror raise diff --git a/flask/ctx.py b/flask/ctx.py index 9e184c18..3438d63f 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -5,7 +5,7 @@ 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. """ diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 9e44fe69..e9765f20 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -5,9 +5,10 @@ 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. """ + import os from warnings import warn diff --git a/flask/globals.py b/flask/globals.py index 0b70a3ef..f99238cd 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -6,7 +6,7 @@ Defines all the global objects that are proxies to the current active context. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/flask/helpers.py b/flask/helpers.py index efec454c..1b8e323b 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -5,7 +5,7 @@ Implements various helpers. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ @@ -22,28 +22,18 @@ import unicodedata from werkzeug.routing import BuildError from functools import update_wrapper -try: - from werkzeug.urls import url_quote -except ImportError: - from urlparse import quote as url_quote - +from werkzeug.urls import url_quote from werkzeug.datastructures import Headers, Range from werkzeug.exceptions import BadRequest, NotFound, \ RequestedRangeNotSatisfiable -# this was moved in 0.7 -try: - from werkzeug.wsgi import wrap_file -except ImportError: - from werkzeug.utils import wrap_file - +from werkzeug.wsgi import wrap_file from jinja2 import FileSystemLoader from .signals import message_flashed from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request -from ._compat import string_types, text_type - +from ._compat import string_types, text_type, PY2 # sentinel _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, '/')) -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') + if not val: - return default + return get_env() == 'development' + 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) if conditional: - if callable(getattr(Range, 'to_content_range_header', None)): - # Werkzeug supports Range Requests - # Remove this test when support for Werkzeug <0.12 is dropped - try: - rv = rv.make_conditional(request, accept_ranges=True, - complete_length=fsize) - except RequestedRangeNotSatisfiable: + try: + rv = rv.make_conditional(request, accept_ranges=True, + complete_length=fsize) + except RequestedRangeNotSatisfiable: + if file is not None: file.close() - raise - else: - rv = rv.make_conditional(request) + raise # make sure we don't send x-sendfile for servers that # ignore the 304 status code for x-sendfile. if rv.status_code == 304: @@ -1001,12 +1002,21 @@ def total_seconds(td): def is_ip(value): """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 :type value: str :return: True if string is an IP address :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): try: diff --git a/flask/json/__init__.py b/flask/json/__init__.py index 6559c1aa..f482c72c 100644 --- a/flask/json/__init__.py +++ b/flask/json/__init__.py @@ -1,4 +1,12 @@ # -*- coding: utf-8 -*- +""" +flask.json +~~~~~~~~~~ + +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. +""" + import io import uuid from datetime import date, datetime @@ -264,7 +272,7 @@ def jsonify(*args, **kwargs): data = args or kwargs 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'] ) diff --git a/flask/json/tag.py b/flask/json/tag.py index 3c57884e..1e51d6fc 100644 --- a/flask/json/tag.py +++ b/flask/json/tag.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ 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) +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. """ from base64 import b64decode, b64encode diff --git a/flask/logging.py b/flask/logging.py index 86a3fa33..389c2c22 100644 --- a/flask/logging.py +++ b/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 import logging diff --git a/flask/sessions.py b/flask/sessions.py index 82b588bc..ec4253d5 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -5,11 +5,13 @@ 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. """ + import hashlib import warnings +from collections import MutableMapping from datetime import datetime from itsdangerous import BadSignature, URLSafeTimedSerializer @@ -19,43 +21,55 @@ from flask.helpers import is_ip, total_seconds from flask.json.tag import TaggedJSONSerializer -class SessionMixin(object): - """Expands a basic dictionary with an accessors that are expected - by Flask extensions and users for the session. - """ +class SessionMixin(MutableMapping): + """Expands a basic dictionary with session attributes.""" - def _get_permanent(self): + @property + def permanent(self): + """This reflects the ``'_permanent'`` key in the dict.""" return self.get('_permanent', False) - def _set_permanent(self, value): + @permanent.setter + def permanent(self, value): self['_permanent'] = bool(value) - #: this reflects the ``'_permanent'`` key in the dict. - permanent = property(_get_permanent, _set_permanent) - del _get_permanent, _set_permanent - - #: 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. + #: Some implementations can detect whether a session is newly + #: created, but that is not guaranteed. Use with caution. The mixin + # default is hard-coded ``False``. new = False - #: for some backends this will always be ``True``, but some backends will - #: default this to false and detect changes in the dictionary for as - #: long as changes do not happen on mutable structures in the session. - #: The default mixin implementation just hardcodes ``True`` in. + #: Some implementations can detect changes to the session and set + #: this when that happens. The mixin default is hard coded to + #: ``True``. modified = True - #: the accessed variable indicates whether or not the session object has - #: been accessed in that request. This allows flask to append a `Vary: - #: Cookie` header to the response if the session is being accessed. This - #: 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. + #: Some implementations can detect when session data is read or + #: written and set this when that happens. The mixin default is hard + #: coded to ``True``. accessed = True 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 on_update(self): @@ -63,8 +77,6 @@ class SecureCookieSession(CallbackDict, SessionMixin): self.accessed = True super(SecureCookieSession, self).__init__(initial, on_update) - self.modified = False - self.accessed = False def __getitem__(self, key): self.accessed = True @@ -238,6 +250,13 @@ class SessionInterface(object): """ 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): """A helper method that returns an expiration date for the session 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) secure = self.get_cookie_secure(app) + samesite = self.get_cookie_samesite(app) expires = self.get_expiration_time(app, session) val = self.get_signing_serializer(app).dumps(dict(session)) response.set_cookie( @@ -360,5 +380,6 @@ class SecureCookieSessionInterface(SessionInterface): httponly=httponly, domain=domain, path=path, - secure=secure + secure=secure, + samesite=samesite ) diff --git a/flask/signals.py b/flask/signals.py index dd52cdb5..18f26307 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -6,9 +6,10 @@ Implements signals based on blinker if available, otherwise 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. """ + signals_available = False try: from blinker import Namespace diff --git a/flask/templating.py b/flask/templating.py index 2da4926d..02402008 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -5,9 +5,10 @@ Implements the bridge to Jinja2. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ + from jinja2 import BaseLoader, Environment as BaseEnvironment, \ TemplateNotFound diff --git a/flask/testing.py b/flask/testing.py index f29c6b17..cd346ce0 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -6,20 +6,19 @@ Implements test support helpers. This module is lazily imported 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. """ import werkzeug from contextlib import contextmanager + +from click.testing import CliRunner +from flask.cli import ScriptInfo from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack from flask.json import dumps as json_dumps - -try: - from werkzeug.urls import url_parse -except ImportError: - from urlparse import urlsplit as url_parse +from werkzeug.urls import url_parse def make_test_environ_builder( @@ -197,3 +196,36 @@ class FlaskClient(Client): top = _request_ctx_stack.top if top is not None and top.preserved: 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 ` 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) diff --git a/flask/views.py b/flask/views.py index b3027970..1f2c997b 100644 --- a/flask/views.py +++ b/flask/views.py @@ -5,9 +5,10 @@ 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. """ + from .globals import request from ._compat import with_metaclass diff --git a/flask/wrappers.py b/flask/wrappers.py index 807059d0..25d119ba 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -5,9 +5,10 @@ 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. """ + from werkzeug.exceptions import BadRequest from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase @@ -122,7 +123,7 @@ class Request(RequestBase, JSONMixin): #: Though if the request's method was invalid for the URL rule, #: the valid list is available in ``routing_exception.valid_methods`` #: instead (an attribute of the Werkzeug exception :exc:`~werkzeug.exceptions.MethodNotAllowed`) - #: because the request was never internally bound. + #: because the request was never internally bound. #: #: .. versionadded:: 0.6 url_rule = None diff --git a/scripts/flaskext_tester.py b/scripts/flaskext_tester.py deleted file mode 100644 index 93ab0ad7..00000000 --- a/scripts/flaskext_tester.py +++ /dev/null @@ -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'''\ - -Flask-Extension Test Results - -

Flask-Extension Test Results

-

- This page contains the detailed test results for the test run of - all {{ 'approved' if approved }} Flask extensions. -

Summary

- - - - - - - {%- for result in results %} - {% set outcome = 'success' if result.success else 'failed' %} - - - {%- endfor %} - -
Extension - Version - Author - License - Outcome - {%- for iptr, _ in results[0].logs|dictsort %} - {{ iptr }} - {%- endfor %} -
{{ result.name }} - {{ result.version }} - {{ result.author }} - {{ result.license }} - {{ outcome }} - {%- for iptr, _ in result.logs|dictsort %} - see log - {%- endfor %} -
-

Test Logs

-

Detailed test logs for all tests on all platforms: -{%- for result in results %} - {%- for iptr, log in result.logs|dictsort %} -

- {{ result.name }} - {{ result.version }} [{{ iptr }}]

-
{{ log }}
- {%- 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() diff --git a/scripts/make-release.py b/scripts/make-release.py old mode 100644 new mode 100755 index fc6421ab..e1ca54f8 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -1,42 +1,41 @@ #!/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 -import sys + import os import re -from datetime import datetime, date -from subprocess import Popen, PIPE +import sys +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(): - with open('CHANGES') as f: + with open('CHANGES.rst') as f: lineiter = iter(f) for line in lineiter: match = re.search('^Version\s+(.*)', line.strip()) + if match is None: continue + version = match.group(1).strip() - if lineiter.next().count('-') != len(match.group(0)): + + if next(lineiter).count('-') != len(match.group(0)): continue + while 1: - change_info = lineiter.next().strip() + change_info = next(lineiter).strip() + if change_info: break - match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)' - r'(?:, codename (.*))?(?i)', change_info) + match = re.search( + r'released on (\w+\s+\d+\w+\s+\d+)(?:, codename (.*))?', + change_info, + flags=re.IGNORECASE + ) + if match is None: continue @@ -46,15 +45,16 @@ def parse_changelog(): def bump_version(version): try: - parts = map(int, version.split('.')) + parts = [int(i) for i in version.split('.')] except ValueError: fail('Current version is not numeric') + parts[-1] += 1 return '.'.join(map(str, parts)) 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') @@ -65,9 +65,13 @@ def set_filename_version(filename, version_number, pattern): before, old, after = match.groups() changed.append(True) return before + version_number + after + with open(filename) as f: - contents = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern, - inject_version, f.read()) + contents = re.sub( + r"^(\s*%s\s*=\s*')(.+?)(')" % pattern, + inject_version, f.read(), + flags=re.DOTALL | re.MULTILINE + ) if not changed: 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__') -def build_and_upload(): - Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel', 'upload']).wait() +def build(): + cmd = [sys.executable, 'setup.py', 'sdist', 'bdist_wheel'] + Popen(cmd).wait() def fail(message, *args): @@ -95,7 +100,9 @@ def info(message, *args): 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(): @@ -116,29 +123,40 @@ def main(): os.chdir(os.path.join(os.path.dirname(__file__), '..')) rv = parse_changelog() + if rv is None: fail('Could not parse changelog') 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)', - version, codename, release_date.strftime('%d/%m/%Y')) + info( + 'Releasing %s (codename %s, release date %s)', + version, codename, release_date.strftime('%d/%m/%Y') + ) tags = get_git_tags() if version in tags: fail('Version "%s" is already tagged', version) + if release_date.date() != date.today(): - fail('Release date is not today (%s != %s)', - release_date.date(), date.today()) + fail( + 'Release date is not today (%s != %s)', + release_date.date(), date.today() + ) if not git_is_clean(): 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) make_git_commit('Bump version number to %s', version) make_git_tag(version) - build_and_upload() + build() set_init_version(dev_version) diff --git a/setup.cfg b/setup.cfg index 527dd3e6..c7641dfc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [aliases] -release = egg_info -RDb '' +release = egg_info -Db '' [bdist_wheel] universal = 1 diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index bb2aab41..4ad54766 --- a/setup.py +++ b/setup.py @@ -1,112 +1,67 @@ -""" -Flask ------ - -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 `. - -Links -````` - -* `website `_ -* `documentation `_ -* `development version - `_ - -""" +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import io import re -import ast 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: - version = str(ast.literal_eval(_version_re.search( - f.read().decode('utf-8')).group(1))) +with io.open('flask/__init__.py', 'rt', encoding='utf8') as f: + version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1) setup( name='Flask', version=version, - url='https://github.com/pallets/flask/', + url='https://www.palletsprojects.com/p/flask/', license='BSD', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', - description='A microframework based on Werkzeug, Jinja2 ' - 'and good intentions', - long_description=__doc__, + maintainer='Pallets team', + maintainer_email='contact@palletsprojects.com', + description='A simple framework for building complex web applications.', + long_description=readme, packages=['flask', 'flask.json'], include_package_data=True, zip_safe=False, platforms='any', install_requires=[ - 'Werkzeug>=0.9', - 'Jinja2>=2.4', - 'itsdangerous>=0.21', - 'click>=4.0', + 'Werkzeug>=0.14', + 'Jinja2>=2.10', + 'itsdangerous>=0.24', + 'click>=5.1', ], extras_require={ 'dotenv': ['python-dotenv'], 'dev': [ - 'blinker', - 'python-dotenv', - 'greenlet', 'pytest>=3', 'coverage', 'tox', 'sphinx', - 'sphinxcontrib-log-cabinet' ], }, classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', + 'Framework :: Flask', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - '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' + 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', + 'Topic :: Software Development :: Libraries :: Application Frameworks', + 'Topic :: Software Development :: Libraries :: Python Modules', ], - entry_points=''' - [console_scripts] - flask=flask.cli:main - ''' + entry_points={ + 'console_scripts': [ + 'flask = flask.cli:main', + ], + }, ) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index edf1abb9..00000000 --- a/test-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -tox -pytest -pytest-cov \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 3a8ae69e..2c19c763 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,19 +3,56 @@ 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. """ -import flask + import gc import os -import sys import pkgutil -import pytest +import sys import textwrap + +import pytest +from _pytest import monkeypatch + +import 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): testing = True secret_key = 'test key' @@ -23,7 +60,7 @@ class Flask(_Flask): @pytest.fixture def app(): - app = Flask(__name__) + app = Flask('flask_test', root_path=os.path.dirname(__file__)) return app diff --git a/tests/test_appctx.py b/tests/test_appctx.py index fc2f6b13..678bf510 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -5,7 +5,7 @@ Tests the application context. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ @@ -215,5 +215,5 @@ def test_clean_pop(app): except ZeroDivisionError: pass - assert called == ['conftest', 'TEARDOWN'] + assert called == ['flask_test', 'TEARDOWN'] assert not flask.current_app diff --git a/tests/test_basic.py b/tests/test_basic.py index 0e3076df..66e0d907 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -5,7 +5,7 @@ The basic functionality. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ @@ -221,12 +221,21 @@ def test_endpoint_decorator(app, client): def test_session(app, client): @app.route('/set', methods=['POST']) def set(): + assert not flask.session.accessed + assert not flask.session.modified flask.session['value'] = flask.request.form['value'] + assert flask.session.accessed + assert flask.session.modified return 'value set' @app.route('/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.get('/get').data == b'42' @@ -310,6 +319,7 @@ def test_session_using_session_settings(app, client): SESSION_COOKIE_DOMAIN='.example.com', SESSION_COOKIE_HTTPONLY=False, SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_SAMESITE='Lax', SESSION_COOKIE_PATH='/' ) @@ -324,6 +334,34 @@ def test_session_using_session_settings(app, client): assert 'path=/' in cookie assert 'secure' 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): @@ -1391,10 +1429,12 @@ def test_request_locals(): 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( SERVER_NAME='localhost.localdomain:5000' ) + client = app.test_client() @app.route('/') def index(): @@ -1745,8 +1785,10 @@ def test_g_iteration_protocol(app_ctx): 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' + client = app.test_client() @app.route('/') def normal_index(): @@ -1763,7 +1805,9 @@ def test_subdomain_basic_support(app, client): 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.route('/', subdomain='') @@ -1774,8 +1818,10 @@ def test_subdomain_matching(app, client): 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' + client = app.test_client() @app.route('/', subdomain='') def index(user): @@ -1785,6 +1831,25 @@ def test_subdomain_matching_with_ports(app, client): 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): @app.route('/') @app.route('//') diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index c58a0d3b..7984a815 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -5,7 +5,7 @@ Blueprints (and currently modules) - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :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' -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.route('/foo', defaults={'baz': 42}) diff --git a/tests/test_cli.py b/tests/test_cli.py index 811ef0c8..f7755258 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,18 +3,19 @@ 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. """ -# -# This file was part of Flask-CLI and was modified under the terms its license, -# the Revised BSD License. -# Copyright (C) 2015 CERN. -# + +# This file was part of Flask-CLI and was modified under the terms of +# its Revised BSD License. Copyright © 2015 CERN. + from __future__ import absolute_import import os +import ssl import sys +import types from functools import partial import click @@ -23,9 +24,11 @@ from _pytest.monkeypatch import notset from click.testing import CliRunner from flask import Flask, current_app -from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, dotenv, \ - find_best_app, get_version, load_dotenv, locate_app, prepare_import, \ +from flask.cli import ( + AppGroup, FlaskGroup, NoAppException, ScriptInfo, dotenv, find_best_app, + get_version, load_dotenv, locate_app, prepare_import, run_command, with_appcontext +) cwd = os.getcwd() 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 def runner(): return CliRunner() @@ -144,6 +134,13 @@ def test_find_best_app(test_apps): 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', ( ('test', cwd, 'test'), @@ -475,3 +472,62 @@ def test_dotenv_optional(monkeypatch): monkeypatch.chdir(test_path) load_dotenv() 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__]) diff --git a/tests/test_config.py b/tests/test_config.py index 1f817c3e..5584ed25 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,11 +3,10 @@ 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. """ - from datetime import timedelta import os import textwrap diff --git a/tests/test_helpers.py b/tests/test_helpers.py index cc480c26..73c43297 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -5,7 +5,7 @@ Various helpers. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ @@ -21,7 +21,7 @@ from werkzeug.http import http_date, parse_cache_control_header, \ import flask 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): @@ -506,7 +506,7 @@ class TestSendfile(object): @pytest.mark.skipif( 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): @app.route('/') @@ -563,7 +563,32 @@ class TestSendfile(object): assert rv.status_code == 200 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): + 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: rv = flask.send_file(f, as_attachment=True, @@ -861,7 +886,7 @@ class TestSafeJoin(object): class TestHelpers(object): @pytest.mark.parametrize('debug, expected_flag, expected_default_flag', [ - ('', None, True), + ('', False, False), ('0', False, False), ('False', False, False), ('No', False, False), @@ -873,7 +898,18 @@ class TestHelpers(object): assert get_debug_flag() is None else: 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): app = flask.Flask(__name__) diff --git a/tests/test_instance_config.py b/tests/test_instance_config.py index 7c2ce6ff..bc912c64 100644 --- a/tests/test_instance_config.py +++ b/tests/test_instance_config.py @@ -3,9 +3,10 @@ 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. """ + import os import sys diff --git a/tests/test_json_tag.py b/tests/test_json_tag.py index b8cb6550..6f42539e 100644 --- a/tests/test_json_tag.py +++ b/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 uuid import uuid4 diff --git a/tests/test_logging.py b/tests/test_logging.py index 1a010569..7577ecec 100644 --- a/tests/test_logging.py +++ b/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 sys @@ -9,14 +18,18 @@ from flask.logging import default_handler, has_level_handler, \ @pytest.fixture(autouse=True) -def reset_logging(monkeypatch): +def reset_logging(pytestconfig): root_handlers = logging.root.handlers[:] + logging.root.handlers = [] root_level = logging.root.level logger = logging.getLogger('flask.app') logger.handlers = [] logger.setLevel(logging.NOTSET) + logging_plugin = pytestconfig.pluginmanager.unregister( + name='logging-plugin') + yield logging.root.handlers[:] = root_handlers @@ -25,6 +38,9 @@ def reset_logging(monkeypatch): logger.handlers = [] logger.setLevel(logging.NOTSET) + if logging_plugin: + pytestconfig.pluginmanager.register(logging_plugin, 'logging-plugin') + def test_logger(app): assert app.logger.name == 'flask.app' diff --git a/tests/test_regression.py b/tests/test_regression.py index d2c088f7..d0765354 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -5,7 +5,7 @@ Tests regressions. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index 87e9a0fb..75d79c67 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -5,7 +5,7 @@ Tests the request context. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/tests/test_signals.py b/tests/test_signals.py index 5e4113f6..fccd8e2f 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -5,7 +5,7 @@ Signalling. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/tests/test_subclassing.py b/tests/test_subclassing.py index 82739a7e..43a1625a 100644 --- a/tests/test_subclassing.py +++ b/tests/test_subclassing.py @@ -6,7 +6,7 @@ Test that certain behavior of flask can be customized by subclasses. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/tests/test_templating.py b/tests/test_templating.py index d871ca4d..5073ae46 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -5,7 +5,7 @@ Template functionality - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/tests/test_testing.py b/tests/test_testing.py index a673c2e1..14c66324 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -5,17 +5,19 @@ Test client and more. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ +import click import pytest import flask import werkzeug from flask._compat import text_type +from flask.cli import ScriptInfo 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): @@ -112,9 +114,11 @@ def test_path_is_url(app): 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['APPLICATION_ROOT'] = '/foo' + client = app.test_client() 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 -def test_subdomain(app, client): +def test_subdomain(): + app = flask.Flask(__name__, subdomain_matching=True) app.config['SERVER_NAME'] = 'example.com' + client = app.test_client() @app.route('/', subdomain='') def view(company_id): @@ -334,3 +340,47 @@ def test_nosubdomain(app, client): assert 200 == response.status_code 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 diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index cd76aa6b..18d5f277 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -1,4 +1,12 @@ # -*- 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 ( Forbidden, InternalServerError, diff --git a/tests/test_views.py b/tests/test_views.py index 4176e542..69bd9131 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -5,7 +5,7 @@ Pluggable views. - :copyright: (c) 2015 by Armin Ronacher. + :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ diff --git a/tox.ini b/tox.ini index 45d81b65..d74c8758 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,14 @@ [tox] envlist = - py{36,35,34,33,27,26,py} + py{36,35,34,27,py} py{36,27,py}-simplejson - py{36,33,27,26,py}-devel - py{36,33,27,26,py}-lowest + py{36,27,py}-devel + py{36,27,py}-lowest docs-html coverage-report [testenv] passenv = LANG -usedevelop = true deps = pytest>=3 coverage @@ -17,10 +16,10 @@ deps = blinker python-dotenv - lowest: Werkzeug==0.9 - lowest: Jinja2==2.4 - lowest: itsdangerous==0.21 - lowest: Click==4.0 + lowest: Werkzeug==0.14 + lowest: Jinja2==2.10 + lowest: itsdangerous==0.24 + lowest: Click==5.1 devel: https://github.com/pallets/werkzeug/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 simplejson: simplejson + commands = # the examples need to be installed to test successfully pip install -e examples/flaskr -q @@ -40,11 +40,11 @@ commands = [testenv:docs-html] 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] 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] deps = coverage @@ -59,8 +59,6 @@ passenv = CI TRAVIS TRAVIS_* deps = codecov skip_install = true 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 report codecov