From 671c67aac96ae2cdf9c554ba4e52c167862be6fe Mon Sep 17 00:00:00 2001 From: Mihir Singh Date: Wed, 16 Apr 2014 17:38:41 -0400 Subject: [PATCH 001/440] Append a 'Vary: Cookie' header to the response when the session has been accessed --- flask/sessions.py | 58 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 82ba3506..99e3980d 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -51,6 +51,13 @@ class SessionMixin(object): #: The default mixin implementation just hardcodes `True` in. 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. + accessed = True class TaggedJSONSerializer(object): """A customized JSON serializer that supports a few extra types that @@ -112,9 +119,18 @@ class SecureCookieSession(CallbackDict, SessionMixin): def __init__(self, initial=None): def on_update(self): self.modified = True + self.accessed = True CallbackDict.__init__(self, initial, on_update) self.modified = False + self.accessed = False + def __getitem__(self, key): + self.accessed = True + return super(SecureCookieSession, self).__getitem__(key) + + def get(self, key, default=None): + self.accessed = True + return super(SecureCookieSession, self).get(key, default) class NullSession(SecureCookieSession): """Class used to generate nicer error messages if sessions are not @@ -334,24 +350,30 @@ class SecureCookieSessionInterface(SessionInterface): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) - # Delete case. If there is no session we bail early. - # If the session was modified to be empty we remove the - # whole cookie. - if not session: - if session.modified: - response.delete_cookie(app.session_cookie_name, - domain=domain, path=path) - return - - # Modification case. There are upsides and downsides to - # emitting a set-cookie header each request. The behavior - # is controlled by the :meth:`should_set_cookie` method - # which performs a quick check to figure out if the cookie - # should be set or not. This is controlled by the - # SESSION_REFRESH_EACH_REQUEST config flag as well as - # the permanent flag on the session itself. - if not self.should_set_cookie(app, session): - return + if session.accessed: + + response.headers.add('Vary', 'Cookie') + + else: + + # Delete case. If there is no session we bail early. + # If the session was modified to be empty we remove the + # whole cookie. + if not session: + if session.modified: + response.delete_cookie(app.session_cookie_name, + domain=domain, path=path) + return + + # Modification case. There are upsides and downsides to + # emitting a set-cookie header each request. The behavior + # is controlled by the :meth:`should_set_cookie` method + # which performs a quick check to figure out if the cookie + # should be set or not. This is controlled by the + # SESSION_REFRESH_EACH_REQUEST config flag as well as + # the permanent flag on the session itself. + if not self.should_set_cookie(app, session): + return httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) From 5935ee495c3e51d26fa2b33de09d532b310cd263 Mon Sep 17 00:00:00 2001 From: Mihir Singh Date: Wed, 16 Apr 2014 17:39:11 -0400 Subject: [PATCH 002/440] Add tests for the `Vary: Cookie` header --- flask/testsuite/basic.py | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index bc0838ad..4ab0bbb3 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -396,6 +396,56 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.config['SESSION_REFRESH_EACH_REQUEST'] = False run_test(expect_header=False) + def test_session_vary_cookie(self): + + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + @app.route('/set-session') + def set_session(): + + flask.session['test'] = 'test' + return '' + + @app.route('/get-session') + def get_session(): + + s = flask.session.get('test') + return '' + + @app.route('/get-session-with-dictionary') + def get_session_with_dictionary(): + + s = flask.session['test'] + return '' + + @app.route('/no-vary-header') + def no_vary_header(): + + return '' + + c = app.test_client() + + rv = c.get('/set-session') + + self.assert_in('Vary', rv.headers) + self.assert_equal('Cookie', rv.headers['Vary']) + + rv = c.get('/get-session') + + self.assert_in('Vary', rv.headers) + self.assert_equal('Cookie', rv.headers['Vary']) + + rv = c.get('/get-session-with-dictionary') + + self.assert_in('Vary', rv.headers) + self.assert_equal('Cookie', rv.headers['Vary']) + + rv = c.get('/no-vary-header') + + self.assert_not_in('Vary', rv.headers) + + def test_flashes(self): app = flask.Flask(__name__) app.secret_key = 'testkey' From 709289037ae149444b18a59975cf9dc6e93cd05a Mon Sep 17 00:00:00 2001 From: Augustus D'Souza Date: Sat, 18 Oct 2014 13:14:04 +0530 Subject: [PATCH 003/440] Corrected api docs http://flask.pocoo.org/docs/0.10/api/#flask.Request.get_json --- flask/wrappers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index e77b9c20..038ba6fc 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -115,8 +115,8 @@ class Request(RequestBase): but this can be overriden by the `force` parameter. :param force: if set to `True` the mimetype is ignored. - :param silent: if set to `False` this method will fail silently - and return `False`. + :param silent: if set to `True` this method will fail silently + and return `None`. :param cache: if set to `True` the parsed JSON data is remembered on the request. """ From c60b5b91e282ce4984fef7cede1a058e66c420df Mon Sep 17 00:00:00 2001 From: Philip House Date: Sun, 1 Feb 2015 15:53:35 -0600 Subject: [PATCH 004/440] Update api.rst, fixed spelling --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 48bb001e..272b193a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -350,7 +350,7 @@ JSON Support Flask uses ``simplejson`` for the JSON implementation. Since simplejson is provided both by the standard library as well as extension Flask will try simplejson first and then fall back to the stdlib json module. On top -of that it will delegate access to the current application's JSOn encoders +of that it will delegate access to the current application's JSON encoders and decoders for easier customization. So for starters instead of doing:: From 887d382da146de0522b1b125db1c3cbe393cc57e Mon Sep 17 00:00:00 2001 From: Jon Banafato Date: Sat, 9 May 2015 12:29:53 -0400 Subject: [PATCH 005/440] Add X-Forwarded-Proto to proxy setup example The ProxyFix middleware provided by Werkzeug uses this header for returning accurate values from request.is_secure and request.scheme. Without adding this header, Flask won't properly detect when it is being served over HTTPS and will fail to generate proper external links and cause certain extensions (e.g. Flask-OAuthlib) to function improperly. Adding this header to the example setup should reduce issues encountered by developers when following this guide. --- docs/deploying/wsgi-standalone.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 5bdd0483..4d006720 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -97,9 +97,10 @@ localhost at port 8000, setting appropriate headers: proxy_pass http://127.0.0.1:8000/; proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; } } From 66b4ea91b4f59c40b36ee2c5a5ccd1218387a891 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 6 Jun 2015 06:25:41 +0200 Subject: [PATCH 006/440] Fix #1195 --- docs/quickstart.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5ed7460f..38c14035 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -118,6 +118,8 @@ The most common reason is a typo or because you did not actually create an Debug Mode ---------- +(Want to just log errors and stack traces? See :ref:`application-errors`) + The :command:`flask` script is nice to start a local development server, but you would have to restart it manually after each change to your code. That is not very nice and Flask can do better. If you enable debug From d13a1b363e1065de98f44fe0446b570ce078007e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 6 Jun 2015 06:29:24 +0200 Subject: [PATCH 007/440] Rename jinja_env_class Inspired by #1056 --- flask/app.py | 4 ++-- tests/test_templating.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index fc1f5505..e260065b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -160,7 +160,7 @@ class Flask(_PackageBoundObject): #: The class that is used for the Jinja environment. #: #: .. versionadded:: 1.0 - jinja_env_class = Environment + jinja_environment = Environment #: The class that is used for the :data:`~flask.g` instance. #: @@ -685,7 +685,7 @@ class Flask(_PackageBoundObject): options['auto_reload'] = self.config['TEMPLATES_AUTO_RELOAD'] else: options['auto_reload'] = self.debug - rv = self.jinja_env_class(self, **options) + rv = self.jinja_environment(self, **options) rv.globals.update( url_for=url_for, get_flashed_messages=get_flashed_messages, diff --git a/tests/test_templating.py b/tests/test_templating.py index 132f42a4..293ca06f 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -367,7 +367,7 @@ def test_custom_jinja_env(): pass class CustomFlask(flask.Flask): - jinja_env_class = CustomEnvironment + jinja_environment = CustomEnvironment app = CustomFlask(__name__) assert isinstance(app.jinja_env, CustomEnvironment) From 805692108ae973281d793250ca883cc1412ab08d Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Mon, 9 Mar 2015 11:22:45 -0400 Subject: [PATCH 008/440] Update send_file() docs to clarify encoding requirement #1286 --- flask/helpers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 79839af7..8457baa2 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -445,14 +445,14 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. versionchanged:: 0.9 cache_timeout pulls its default from application config, when None. - :param filename_or_fp: the filename of the file to send. This is - relative to the :attr:`~Flask.root_path` if a - relative path is specified. - Alternatively a file object might be provided - in which case ``X-Sendfile`` might not work and - fall back to the traditional method. Make sure - that the file pointer is positioned at the start - of data to send before calling :func:`send_file`. + :param filename_or_fp: the filename of the file to send in `latin-1`. + This is relative to the :attr:`~Flask.root_path` + if a relative path is specified. + Alternatively a file object might be provided in + which case ``X-Sendfile`` might not work and fall + back to the traditional method. Make sure that the + file pointer is positioned at the start of data to + send before calling :func:`send_file`. :param mimetype: the mimetype of the file if provided, otherwise auto detection happens. :param as_attachment: set to ``True`` if you want to send this file with From b471df6c885abc08f59abcf21b9b77c902cc894b Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 8 Jun 2015 15:18:34 +0200 Subject: [PATCH 009/440] Point to stable version of Celery --- docs/patterns/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index 17abcbaa..52155f62 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -6,7 +6,7 @@ have a Flask integration but it became unnecessary after some restructuring of the internals of Celery with Version 3. This guide fills in the blanks in how to properly use Celery with Flask but assumes that you generally already read the `First Steps with Celery -`_ +`_ guide in the official Celery documentation. Installing Celery From a4b335a64a6a34de36a2fcd93dc8118bd58dd733 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 10 Jun 2015 10:58:49 +0200 Subject: [PATCH 010/440] Remove the word `trivially` here A lot of things are trivial, or debatably trivial, but this is not one of them :) --- docs/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index e0853b96..6387202b 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -274,7 +274,7 @@ this code to get the current user:: return user For a test it would be nice to override this user from the outside without -having to change some code. This can trivially be accomplished with +having to change some code. This can be accomplished with hooking the :data:`flask.appcontext_pushed` signal:: from contextlib import contextmanager From beec47a7cc56860435d142e68324a11ffd915cac Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 10 Jun 2015 18:37:07 +0200 Subject: [PATCH 011/440] Deduplicate signals docs Triggered by #1390 --- docs/api.rst | 126 ++++++++++++++++++++++++++++++------ docs/signals.rst | 164 +---------------------------------------------- 2 files changed, 109 insertions(+), 181 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 5ad17401..69ef38b5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -508,36 +508,65 @@ Useful Internals .. autoclass:: flask.blueprints.BlueprintSetupState :members: +.. _core-signals-list: + Signals ------- -.. when modifying this list, also update the one in signals.rst - .. versionadded:: 0.6 -.. data:: signals_available +.. data:: signals.signals_available ``True`` if the signaling system is available. This is the case when `blinker`_ is installed. +The following signals exist in Flask: + .. data:: template_rendered This signal is sent when a template was successfully rendered. The signal is invoked with the instance of the template as `template` and the context as dictionary (named `context`). + Example subscriber:: + + def log_template_renders(sender, template, context, **extra): + sender.logger.debug('Rendering template "%s" with context %s', + template.name or 'string template', + context) + + from flask import template_rendered + template_rendered.connect(log_template_renders, app) + .. data:: request_started - This signal is sent before any request processing started but when the - request context was set up. Because the request context is already + This signal is sent when the request context is set up, before + any request processing happens. Because the request context is already bound, the subscriber can access the request with the standard global proxies such as :class:`~flask.request`. + Example subscriber:: + + def log_request(sender, **extra): + sender.logger.debug('Request context is set up') + + from flask import request_started + request_started.connect(log_request, app) + .. data:: request_finished This signal is sent right before the response is sent to the client. It is passed the response to be sent named `response`. + Example subscriber:: + + def log_response(sender, response, **extra): + sender.logger.debug('Request context is about to close down. ' + 'Response: %s', response) + + from flask import request_finished + request_finished.connect(log_response, app) + .. data:: got_request_exception This signal is sent when an exception happens during request processing. @@ -545,26 +574,77 @@ Signals in debug mode, where no exception handling happens. The exception itself is passed to the subscriber as `exception`. + Example subscriber:: + + def log_exception(sender, exception, **extra): + sender.logger.debug('Got exception during processing: %s', exception) + + from flask import got_request_exception + got_request_exception.connect(log_exception, app) + .. data:: request_tearing_down - This signal is sent when the application is tearing down the request. - This is always called, even if an error happened. An `exc` keyword - argument is passed with the exception that caused the teardown. + This signal is sent when the request is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender, **extra): + session.close() + + from flask import request_tearing_down + request_tearing_down.connect(close_db_connection, app) - .. versionchanged:: 0.9 - The `exc` parameter was added. + As of Flask 0.9, this will also be passed an `exc` keyword argument + that has a reference to the exception that caused the teardown if + there was one. .. data:: appcontext_tearing_down - This signal is sent when the application is tearing down the - application context. This is always called, even if an error happened. - An `exc` keyword argument is passed with the exception that caused the - teardown. The sender is the application. + This signal is sent when the app context is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender, **extra): + session.close() + + from flask import appcontext_tearing_down + appcontext_tearing_down.connect(close_db_connection, app) + + This will also be passed an `exc` keyword argument that has a reference + to the exception that caused the teardown if there was one. .. data:: appcontext_pushed This signal is sent when an application context is pushed. The sender - is the application. + is the application. This is usually useful for unittests in order to + temporarily hook in information. For instance it can be used to + set a resource early onto the `g` object. + + Example usage:: + + from contextlib import contextmanager + from flask import appcontext_pushed + + @contextmanager + def user_set(app, user): + def handler(sender, **kwargs): + g.user = user + with appcontext_pushed.connected_to(handler, app): + yield + + And in the testcode:: + + def test_user_me(self): + with user_set(app, 'john'): + c = app.test_client() + resp = c.get('/users/me') + assert resp.data == 'username=john' .. versionadded:: 0.10 @@ -576,17 +656,25 @@ Signals .. versionadded:: 0.10 + .. data:: message_flashed This signal is sent when the application is flashing a message. The messages is sent as `message` keyword argument and the category as `category`. - .. versionadded:: 0.10 + Example subscriber:: -.. currentmodule:: None + recorded = [] + def record(sender, message, category, **extra): + recorded.append((message, category)) -.. class:: flask.signals.Namespace + from flask import message_flashed + message_flashed.connect(record, app) + + .. versionadded:: 0.10 + +.. class:: signals.Namespace An alias for :class:`blinker.base.Namespace` if blinker is available, otherwise a dummy class that creates fake signals. This class is @@ -600,8 +688,10 @@ Signals do nothing but will fail with a :exc:`RuntimeError` for all other operations, including connecting. + .. _blinker: https://pypi.python.org/pypi/blinker + Class-Based Views ----------------- diff --git a/docs/signals.rst b/docs/signals.rst index ecb49d5f..b368194c 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -184,169 +184,7 @@ With Blinker 1.1 you can also easily subscribe to signals by using the new Core Signals ------------ -.. when modifying this list, also update the one in api.rst +Take a look at :ref:`core-signals-list` for a list of all builtin signals. -The following signals exist in Flask: - -.. data:: flask.template_rendered - :noindex: - - This signal is sent when a template was successfully rendered. The - signal is invoked with the instance of the template as `template` - and the context as dictionary (named `context`). - - Example subscriber:: - - def log_template_renders(sender, template, context, **extra): - sender.logger.debug('Rendering template "%s" with context %s', - template.name or 'string template', - context) - - from flask import template_rendered - template_rendered.connect(log_template_renders, app) - -.. data:: flask.request_started - :noindex: - - This signal is sent when the request context is set up, before - any request processing happens. Because the request context is already - bound, the subscriber can access the request with the standard global - proxies such as :class:`~flask.request`. - - Example subscriber:: - - def log_request(sender, **extra): - sender.logger.debug('Request context is set up') - - from flask import request_started - request_started.connect(log_request, app) - -.. data:: flask.request_finished - :noindex: - - This signal is sent right before the response is sent to the client. - It is passed the response to be sent named `response`. - - Example subscriber:: - - def log_response(sender, response, **extra): - sender.logger.debug('Request context is about to close down. ' - 'Response: %s', response) - - from flask import request_finished - request_finished.connect(log_response, app) - -.. data:: flask.got_request_exception - :noindex: - - This signal is sent when an exception happens during request processing. - It is sent *before* the standard exception handling kicks in and even - in debug mode, where no exception handling happens. The exception - itself is passed to the subscriber as `exception`. - - Example subscriber:: - - def log_exception(sender, exception, **extra): - sender.logger.debug('Got exception during processing: %s', exception) - - from flask import got_request_exception - got_request_exception.connect(log_exception, app) - -.. data:: flask.request_tearing_down - :noindex: - - This signal is sent when the request is tearing down. This is always - called, even if an exception is caused. Currently functions listening - to this signal are called after the regular teardown handlers, but this - is not something you can rely on. - - Example subscriber:: - - def close_db_connection(sender, **extra): - session.close() - - from flask import request_tearing_down - request_tearing_down.connect(close_db_connection, app) - - As of Flask 0.9, this will also be passed an `exc` keyword argument - that has a reference to the exception that caused the teardown if - there was one. - -.. data:: flask.appcontext_tearing_down - :noindex: - - This signal is sent when the app context is tearing down. This is always - called, even if an exception is caused. Currently functions listening - to this signal are called after the regular teardown handlers, but this - is not something you can rely on. - - Example subscriber:: - - def close_db_connection(sender, **extra): - session.close() - - from flask import appcontext_tearing_down - appcontext_tearing_down.connect(close_db_connection, app) - - This will also be passed an `exc` keyword argument that has a reference - to the exception that caused the teardown if there was one. - -.. data:: flask.appcontext_pushed - :noindex: - - This signal is sent when an application context is pushed. The sender - is the application. This is usually useful for unittests in order to - temporarily hook in information. For instance it can be used to - set a resource early onto the `g` object. - - Example usage:: - - from contextlib import contextmanager - from flask import appcontext_pushed - - @contextmanager - def user_set(app, user): - def handler(sender, **kwargs): - g.user = user - with appcontext_pushed.connected_to(handler, app): - yield - - And in the testcode:: - - def test_user_me(self): - with user_set(app, 'john'): - c = app.test_client() - resp = c.get('/users/me') - assert resp.data == 'username=john' - - .. versionadded:: 0.10 - -.. data:: flask.appcontext_popped - :noindex: - - This signal is sent when an application context is popped. The sender - is the application. This usually falls in line with the - :data:`appcontext_tearing_down` signal. - - .. versionadded:: 0.10 - - -.. data:: flask.message_flashed - :noindex: - - This signal is sent when the application is flashing a message. The - messages is sent as `message` keyword argument and the category as - `category`. - - Example subscriber:: - - recorded = [] - def record(sender, message, category, **extra): - recorded.append((message, category)) - - from flask import message_flashed - message_flashed.connect(record, app) - - .. versionadded:: 0.10 .. _blinker: https://pypi.python.org/pypi/blinker From bc4c1777e9aaee1c404ab06dc92893da21f2cea7 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 11 Jun 2015 19:55:51 +0200 Subject: [PATCH 012/440] Document static_folder --- flask/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 8457baa2..861b21ab 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -798,7 +798,9 @@ class _PackageBoundObject(object): return os.path.join(self.root_path, self._static_folder) def _set_static_folder(self, value): self._static_folder = value - static_folder = property(_get_static_folder, _set_static_folder) + static_folder = property(_get_static_folder, _set_static_folder, doc=''' + The absolute path to the configured static folder. + ''') del _get_static_folder, _set_static_folder def _get_static_url_path(self): From c65b32ba1d71c95d165f173f8681bfc0f6e1eca2 Mon Sep 17 00:00:00 2001 From: GunWoo Choi Date: Fri, 12 Jun 2015 13:40:53 +0900 Subject: [PATCH 013/440] Update title of docstring in flask.cli --- flask/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 1b86645e..360ce12e 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ - flask.run + flask.cli ~~~~~~~~~ A simple command line application to run flask apps. From 554c5b965a8b9d702897e5da926312cac22fa86e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 13 Jun 2015 17:07:38 +0200 Subject: [PATCH 014/440] Fix #1477 --- flask/blueprints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/blueprints.py b/flask/blueprints.py index 3c3cf7c6..c0d47476 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -101,7 +101,6 @@ class Blueprint(_PackageBoundObject): self.static_folder = static_folder self.static_url_path = static_url_path self.deferred_functions = [] - self.view_functions = {} if url_defaults is None: url_defaults = {} self.url_values_defaults = url_defaults From 30973310ec2aaa12bce5c1293c802ab0c662b83a Mon Sep 17 00:00:00 2001 From: Menghan Date: Sat, 13 Jun 2015 23:16:14 +0800 Subject: [PATCH 015/440] Replace 'Werkzeug' to 'Flask' --- CONTRIBUTING.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 58508252..419c70cb 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -2,7 +2,7 @@ How to contribute to Flask ========================== -Thanks for considering contributing to Flask. +Thanks for considering contributing to Flask. Support questions ================= @@ -49,16 +49,16 @@ Clone this repository:: Install Flask as an editable package using the current source:: - pip install --editable . + pip install --editable . Then you can run the testsuite with:: py.test With only py.test installed, a large part of the testsuite will get skipped -though. Whether this is relevant depends on which part of Werkzeug you're -working on. Travis is set up to run the full testsuite when you submit your -pull request anyways. +though. Whether this is relevant depends on which part of Flask you're working +on. Travis is set up to run the full testsuite when you submit your pull +request anyways. If you really want to test everything, you will have to install ``tox`` instead of ``pytest``. Currently we're depending on a development version of Tox From 284081c452e96b31ae43df2e30a162acec72728c Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 17 Jun 2015 13:01:47 +0200 Subject: [PATCH 016/440] Don't pass version to tox explicitly --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3ab8265b..b9a4eb25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,11 +23,10 @@ matrix: install: - - pip install tox>=1.8 + - pip install tox script: - - tox -e \ - $(echo py$TRAVIS_PYTHON_VERSION | tr -d . | sed -e 's/pypypy/pypy/')-$REQUIREMENTS + - tox -e py-$REQUIREMENTS branches: except: From d53d5c732bf794b965d15458f8e578fe51864a26 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Mon, 23 Mar 2015 08:09:21 +0000 Subject: [PATCH 017/440] before_render_template signal --- flask/__init__.py | 2 +- flask/signals.py | 3 ++- flask/templating.py | 3 ++- tests/test_signals.py | 24 ++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index a6ef98ca..7fd7a253 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -34,7 +34,7 @@ from .templating import render_template, render_template_string from .signals import signals_available, template_rendered, request_started, \ request_finished, got_request_exception, request_tearing_down, \ appcontext_tearing_down, appcontext_pushed, \ - appcontext_popped, message_flashed + appcontext_popped, message_flashed, before_render_template # We're not exposing the actual json module but a convenient wrapper around # it. diff --git a/flask/signals.py b/flask/signals.py index ca1c1d90..4a2cfe07 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -45,6 +45,7 @@ _signals = Namespace() # Core signals. For usage examples grep the source code or consult # the API documentation in docs/api.rst as well as docs/signals.rst template_rendered = _signals.signal('template-rendered') +before_render_template = _signals.signal('before-render-template') request_started = _signals.signal('request-started') request_finished = _signals.signal('request-finished') request_tearing_down = _signals.signal('request-tearing-down') @@ -52,4 +53,4 @@ got_request_exception = _signals.signal('got-request-exception') appcontext_tearing_down = _signals.signal('appcontext-tearing-down') appcontext_pushed = _signals.signal('appcontext-pushed') appcontext_popped = _signals.signal('appcontext-popped') -message_flashed = _signals.signal('message-flashed') +message_flashed = _signals.signal('message-flashed') \ No newline at end of file diff --git a/flask/templating.py b/flask/templating.py index 1e39b932..74ff104f 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -12,7 +12,7 @@ from jinja2 import BaseLoader, Environment as BaseEnvironment, \ TemplateNotFound from .globals import _request_ctx_stack, _app_ctx_stack -from .signals import template_rendered +from .signals import template_rendered, before_render_template def _default_template_ctx_processor(): @@ -102,6 +102,7 @@ class DispatchingJinjaLoader(BaseLoader): def _render(template, context, app): """Renders the template and fires the signal""" + before_render_template.send(app, template=template, context=context) rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv diff --git a/tests/test_signals.py b/tests/test_signals.py index e17acfd4..b687b6e8 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -46,6 +46,30 @@ def test_template_rendered(): finally: flask.template_rendered.disconnect(record, app) +def test_before_render_template(): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return flask.render_template('simple_template.html', whiskey=42) + + recorded = [] + + def record(sender, template, context): + context['whiskey'] = 43 + recorded.append((template, context)) + + flask.before_render_template.connect(record, app) + try: + rv = app.test_client().get('/') + assert len(recorded) == 1 + template, context = recorded[0] + assert template.name == 'simple_template.html' + assert context['whiskey'] == 43 + assert rv.data == b'

43

' + finally: + flask.before_render_template.disconnect(record, app) + def test_request_signals(): app = flask.Flask(__name__) calls = [] From 1fbeb337c467a2be0f999606373fd1ba0e200908 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Mon, 23 Mar 2015 12:25:53 +0000 Subject: [PATCH 018/440] fix endline in the signal.py --- flask/signals.py | 2 +- tests/test_signals.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/flask/signals.py b/flask/signals.py index 4a2cfe07..c9b8a210 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -53,4 +53,4 @@ got_request_exception = _signals.signal('got-request-exception') appcontext_tearing_down = _signals.signal('appcontext-tearing-down') appcontext_pushed = _signals.signal('appcontext-pushed') appcontext_popped = _signals.signal('appcontext-popped') -message_flashed = _signals.signal('message-flashed') \ No newline at end of file +message_flashed = _signals.signal('message-flashed') diff --git a/tests/test_signals.py b/tests/test_signals.py index b687b6e8..bab5b155 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -59,16 +59,14 @@ def test_before_render_template(): context['whiskey'] = 43 recorded.append((template, context)) - flask.before_render_template.connect(record, app) - try: + with flask.before_render_template.connected_to(record): rv = app.test_client().get('/') assert len(recorded) == 1 template, context = recorded[0] assert template.name == 'simple_template.html' assert context['whiskey'] == 43 assert rv.data == b'

43

' - finally: - flask.before_render_template.disconnect(record, app) + def test_request_signals(): app = flask.Flask(__name__) From 967907ee81cbc671cd8982bdc02a9d3335fdde11 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 24 Mar 2015 12:20:28 +0000 Subject: [PATCH 019/440] before_render_template signal can override render template. --- flask/templating.py | 15 +++++++++++++-- tests/test_signals.py | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index 74ff104f..d76d82a0 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -101,8 +101,19 @@ class DispatchingJinjaLoader(BaseLoader): def _render(template, context, app): - """Renders the template and fires the signal""" - before_render_template.send(app, template=template, context=context) + """Renders the template and fires signals. + + It is possible to override render template in the before_render_template signal. + It can be done only if exactly one receiver and it return not None result.""" + + brt_resp = before_render_template.send(app, template=template, context=context) + + if len(brt_resp) == 1: + first_resp = brt_resp[0] + + if len(first_resp) == 2 and first_resp[1] is not None: + return first_resp[1] + rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv diff --git a/tests/test_signals.py b/tests/test_signals.py index bab5b155..f77e645f 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -67,6 +67,22 @@ def test_before_render_template(): assert context['whiskey'] == 43 assert rv.data == b'

43

' +def test_before_render_template_signal_not_None_result_render_skip_render_template(): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return flask.render_template('simple_template.html', whiskey=42) + + recorded = [] + + def record(sender, template, context): + recorded.append((template, context)) + return 'Not template string' + + with flask.before_render_template.connected_to(record): + rv = app.test_client().get('/') + assert rv.data == 'Not template string' def test_request_signals(): app = flask.Flask(__name__) From e57199e0c4e18da2a87b15ed6d6e35668ae1b763 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 24 Mar 2015 13:49:39 +0000 Subject: [PATCH 020/440] fix test_signals --- tests/test_signals.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_signals.py b/tests/test_signals.py index f77e645f..7192adc8 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -59,13 +59,16 @@ def test_before_render_template(): context['whiskey'] = 43 recorded.append((template, context)) - with flask.before_render_template.connected_to(record): + flask.before_render_template.connect(record, app) + try: rv = app.test_client().get('/') assert len(recorded) == 1 template, context = recorded[0] assert template.name == 'simple_template.html' assert context['whiskey'] == 43 assert rv.data == b'

43

' + finally: + flask.before_render_template.disconnect(record, app) def test_before_render_template_signal_not_None_result_render_skip_render_template(): app = flask.Flask(__name__) @@ -80,9 +83,12 @@ def test_before_render_template_signal_not_None_result_render_skip_render_templa recorded.append((template, context)) return 'Not template string' - with flask.before_render_template.connected_to(record): + flask.before_render_template.connect(record, app) + try: rv = app.test_client().get('/') assert rv.data == 'Not template string' + finally: + flask.before_render_template.disconnect(record, app) def test_request_signals(): app = flask.Flask(__name__) From eae37b575d2106ae80edea823d50c0e4ebfebec3 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 24 Mar 2015 14:28:33 +0000 Subject: [PATCH 021/440] fix test_signals --- tests/test_signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_signals.py b/tests/test_signals.py index 7192adc8..e85c66e6 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -86,7 +86,7 @@ def test_before_render_template_signal_not_None_result_render_skip_render_templa flask.before_render_template.connect(record, app) try: rv = app.test_client().get('/') - assert rv.data == 'Not template string' + assert rv.data == b'Not template string' finally: flask.before_render_template.disconnect(record, app) From 883f82f2617f893d024b3fcee8fd9bbe0b0d9341 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Mon, 30 Mar 2015 10:49:55 +0000 Subject: [PATCH 022/440] template overrides handling changed --- flask/templating.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index d76d82a0..0c54cbd4 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -108,11 +108,12 @@ def _render(template, context, app): brt_resp = before_render_template.send(app, template=template, context=context) - if len(brt_resp) == 1: - first_resp = brt_resp[0] - - if len(first_resp) == 2 and first_resp[1] is not None: - return first_resp[1] + overrides = [rv for _, rv in brt_resp if rv is not None] + if len(overrides) == 1: + return overrides[0] + elif len(overrides) > 1: + raise RuntimeError('More than one before_render_template signal ' + 'returned data') rv = template.render(context) template_rendered.send(app, template=template, context=context) From 5e12748d0efb5fc3d1f6a68d7bf05fd8980a2fd4 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Tue, 7 Apr 2015 07:18:15 +0000 Subject: [PATCH 023/440] Ignore before_render_template return values --- flask/templating.py | 15 ++------------- tests/test_signals.py | 20 -------------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index 0c54cbd4..59fd988e 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -101,20 +101,9 @@ class DispatchingJinjaLoader(BaseLoader): def _render(template, context, app): - """Renders the template and fires signals. - - It is possible to override render template in the before_render_template signal. - It can be done only if exactly one receiver and it return not None result.""" - - brt_resp = before_render_template.send(app, template=template, context=context) - - overrides = [rv for _, rv in brt_resp if rv is not None] - if len(overrides) == 1: - return overrides[0] - elif len(overrides) > 1: - raise RuntimeError('More than one before_render_template signal ' - 'returned data') + """Renders the template and fires the signal""" + before_render_template.send(app, template=template, context=context) rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv diff --git a/tests/test_signals.py b/tests/test_signals.py index e85c66e6..b687b6e8 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -70,26 +70,6 @@ def test_before_render_template(): finally: flask.before_render_template.disconnect(record, app) -def test_before_render_template_signal_not_None_result_render_skip_render_template(): - app = flask.Flask(__name__) - - @app.route('/') - def index(): - return flask.render_template('simple_template.html', whiskey=42) - - recorded = [] - - def record(sender, template, context): - recorded.append((template, context)) - return 'Not template string' - - flask.before_render_template.connect(record, app) - try: - rv = app.test_client().get('/') - assert rv.data == b'Not template string' - finally: - flask.before_render_template.disconnect(record, app) - def test_request_signals(): app = flask.Flask(__name__) calls = [] From a9066a37563f40ec6757e99c458f5883bb000bd1 Mon Sep 17 00:00:00 2001 From: Alexander Pantyukhin Date: Wed, 17 Jun 2015 11:59:04 +0000 Subject: [PATCH 024/440] Changes and docs are modified. --- CHANGES | 1 + docs/api.rst | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGES b/CHANGES index 284c95d0..a8543e6c 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Version 1.0 (release date to be announced, codename to be selected) +- Added before_render_template signal. - Added `**kwargs` to :meth:`flask.Test.test_client` to support passing additional keyword arguments to the constructor of :attr:`flask.Flask.test_client_class`. diff --git a/docs/api.rst b/docs/api.rst index 69ef38b5..70be5ca2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -538,6 +538,23 @@ The following signals exist in Flask: from flask import template_rendered template_rendered.connect(log_template_renders, app) +.. data:: flask.before_render_template + :noindex: + + This signal is sent before template rendering process. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + + Example subscriber:: + + def log_template_renders(sender, template, context, **extra): + sender.logger.debug('Rendering template "%s" with context %s', + template.name or 'string template', + context) + + from flask import before_render_template + before_render_template.connect(log_template_renders, app) + .. data:: request_started This signal is sent when the request context is set up, before From bbaf20de7c34d533f9b83f20b536342a8c735fb1 Mon Sep 17 00:00:00 2001 From: ThiefMaster Date: Sat, 20 Jun 2015 17:49:50 +0200 Subject: [PATCH 025/440] Add pop and setdefault to AppCtxGlobals --- CHANGES | 1 + docs/api.rst | 3 +++ flask/ctx.py | 9 +++++++++ tests/test_appctx.py | 22 ++++++++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/CHANGES b/CHANGES index a8543e6c..8310761f 100644 --- a/CHANGES +++ b/CHANGES @@ -67,6 +67,7 @@ Version 1.0 - Don't leak exception info of already catched exceptions to context teardown handlers (pull request ``#1393``). - Allow custom Jinja environment subclasses (pull request ``#1422``). +- ``flask.g`` now has ``pop()`` and ``setdefault`` methods. Version 0.10.2 -------------- diff --git a/docs/api.rst b/docs/api.rst index 70be5ca2..3da975e9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -289,6 +289,9 @@ thing, like it does for :class:`request` and :class:`session`. It's now also possible to use the ``in`` operator on it to see if an attribute is defined and it yields all keys on iteration. + As of 1.0 you can use :meth:`pop` and :meth:`setdefault` in the same + way you would use them on a dictionary. + This is a proxy. See :ref:`notes-on-proxies` for more information. diff --git a/flask/ctx.py b/flask/ctx.py index 24d0612c..91b5ee50 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -31,6 +31,15 @@ class _AppCtxGlobals(object): def get(self, name, default=None): return self.__dict__.get(name, default) + def pop(self, name, default=_sentinel): + if default is _sentinel: + return self.__dict__.pop(name) + else: + return self.__dict__.pop(name, default) + + def setdefault(self, name, default=None): + self.__dict__.setdefault(name, default) + def __contains__(self, item): return item in self.__dict__ diff --git a/tests/test_appctx.py b/tests/test_appctx.py index 2b0f5f94..f24704ef 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -93,6 +93,28 @@ def test_app_tearing_down_with_handled_exception(): assert cleanup_stuff == [None] +def test_app_ctx_globals_methods(): + app = flask.Flask(__name__) + with app.app_context(): + # get + assert flask.g.get('foo') is None + assert flask.g.get('foo', 'bar') == 'bar' + # __contains__ + assert 'foo' not in flask.g + flask.g.foo = 'bar' + assert 'foo' in flask.g + # setdefault + flask.g.setdefault('bar', 'the cake is a lie') + flask.g.setdefault('bar', 'hello world') + assert flask.g.bar == 'the cake is a lie' + # pop + assert flask.g.pop('bar') == 'the cake is a lie' + with pytest.raises(KeyError): + flask.g.pop('bar') + assert flask.g.pop('bar', 'more cake') == 'more cake' + # __iter__ + assert list(flask.g) == ['foo'] + def test_custom_app_ctx_globals_class(): class CustomRequestGlobals(object): def __init__(self): From 6af9690ae9800f5a6074c70498cba4b26335ec72 Mon Sep 17 00:00:00 2001 From: ThiefMaster Date: Sat, 20 Jun 2015 18:09:27 +0200 Subject: [PATCH 026/440] Remove the deprecated Flask.modules property --- flask/app.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/flask/app.py b/flask/app.py index e260065b..668f36ce 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1963,13 +1963,6 @@ class Flask(_PackageBoundObject): error = None ctx.auto_pop(error) - @property - def modules(self): - from warnings import warn - warn(DeprecationWarning('Flask.modules is deprecated, use ' - 'Flask.blueprints instead'), stacklevel=2) - return self.blueprints - def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`.""" return self.wsgi_app(environ, start_response) From 5dfe918e4fafd63fc77664a3a1cda501bb0929f9 Mon Sep 17 00:00:00 2001 From: Zev Averbach Date: Fri, 26 Jun 2015 08:41:56 -0400 Subject: [PATCH 027/440] fixed some punctuation, fixed a few errors, in service of readability --- docs/patterns/wtforms.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 38e652e8..88602b6c 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -1,7 +1,7 @@ Form Validation with WTForms ============================ -When you have to work with form data submitted by a browser view code +When you have to work with form data submitted by a browser view, code quickly becomes very hard to read. There are libraries out there designed to make this process easier to manage. One of them is `WTForms`_ which we will handle here. If you find yourself in the situation of having many @@ -12,10 +12,10 @@ first. I recommend breaking up the application into multiple modules (:ref:`larger-applications`) for that and adding a separate module for the forms. -.. admonition:: Getting most of WTForms with an Extension +.. admonition:: Getting the most out of WTForms with an Extension - The `Flask-WTF`_ extension expands on this pattern and adds a few - handful little helpers that make working with forms and Flask more + The `Flask-WTF`_ extension expands on this pattern and adds a + few little helpers that make working with forms and Flask more fun. You can get it from `PyPI `_. @@ -54,8 +54,8 @@ In the view function, the usage of this form looks like this:: return redirect(url_for('login')) return render_template('register.html', form=form) -Notice that we are implying that the view is using SQLAlchemy here -(:ref:`sqlalchemy-pattern`) but this is no requirement of course. Adapt +Notice we're implying that the view is using SQLAlchemy here +(:ref:`sqlalchemy-pattern`), but that's not a requirement, of course. Adapt the code as necessary. Things to remember: @@ -64,14 +64,14 @@ Things to remember: the data is submitted via the HTTP ``POST`` method and :attr:`~flask.request.args` if the data is submitted as ``GET``. 2. to validate the data, call the :func:`~wtforms.form.Form.validate` - method which will return ``True`` if the data validates, ``False`` + method, which will return ``True`` if the data validates, ``False`` otherwise. 3. to access individual values from the form, access `form..data`. Forms in Templates ------------------ -Now to the template side. When you pass the form to the templates you can +Now to the template side. When you pass the form to the templates, you can easily render them there. Look at the following example template to see how easy this is. WTForms does half the form generation for us already. To make it even nicer, we can write a macro that renders a field with @@ -95,14 +95,14 @@ Here's an example :file:`_formhelpers.html` template with such a macro: {% endmacro %} This macro accepts a couple of keyword arguments that are forwarded to -WTForm's field function that renders the field for us. The keyword -arguments will be inserted as HTML attributes. So for example you can +WTForm's field function, which renders the field for us. The keyword +arguments will be inserted as HTML attributes. So, for example, you can call ``render_field(form.username, class='username')`` to add a class to the input element. Note that WTForms returns standard Python unicode -strings, so we have to tell Jinja2 that this data is already HTML escaped +strings, so we have to tell Jinja2 that this data is already HTML-escaped with the ``|safe`` filter. -Here the :file:`register.html` template for the function we used above which +Here is the :file:`register.html` template for the function we used above, which takes advantage of the :file:`_formhelpers.html` template: .. sourcecode:: html+jinja From e4f635f8d7bade2dbd8b7e3e8be6df41e058c797 Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Tue, 30 Jun 2015 10:59:25 -0700 Subject: [PATCH 028/440] remove whitespace at end of lines --- flask/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/flask/app.py b/flask/app.py index 668f36ce..dae6b24e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1090,14 +1090,14 @@ class Flask(_PackageBoundObject): exc_class = default_exceptions[exc_class_or_code] else: exc_class = exc_class_or_code - + assert issubclass(exc_class, Exception) - + if issubclass(exc_class, HTTPException): return exc_class, exc_class.code else: return exc_class, None - + @setupmethod def errorhandler(self, code_or_exception): """A decorator that is used to register a function give a given @@ -1166,9 +1166,9 @@ class Flask(_PackageBoundObject): 'Tried to register a handler for an exception instance {0!r}. ' 'Handlers can only be registered for exception classes or HTTP error codes.' .format(code_or_exception)) - + exc_class, code = self._get_exc_class_and_code(code_or_exception) - + handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {}) handlers[exc_class] = f @@ -1460,7 +1460,7 @@ class Flask(_PackageBoundObject): # those unchanged as errors if e.code is None: return e - + handler = self._find_error_handler(e) if handler is None: return e @@ -1503,12 +1503,12 @@ class Flask(_PackageBoundObject): # wants the traceback preserved in handle_http_exception. Of course # we cannot prevent users from trashing it themselves in a custom # trap_http_exception method so that's their fault then. - + if isinstance(e, HTTPException) and not self.trap_http_exception(e): return self.handle_http_exception(e) handler = self._find_error_handler(e) - + if handler is None: reraise(exc_type, exc_value, tb) return handler(e) From 99c99c4c16b1327288fd76c44bc8635a1de452bc Mon Sep 17 00:00:00 2001 From: Alan Hamlett Date: Tue, 30 Jun 2015 11:00:14 -0700 Subject: [PATCH 029/440] Enable autoescape for `render_template_string` --- CHANGES | 2 ++ docs/templating.rst | 5 ++++- docs/upgrading.rst | 4 ++++ flask/app.py | 4 ++-- flask/templating.py | 2 +- tests/templates/non_escaping_template.txt | 8 ++++++++ tests/test_templating.py | 21 ++++++++++++++++++++- 7 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 tests/templates/non_escaping_template.txt diff --git a/CHANGES b/CHANGES index 8310761f..b33c3795 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,8 @@ Version 1.0 handlers (pull request ``#1393``). - Allow custom Jinja environment subclasses (pull request ``#1422``). - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. +- Turn on autoescape for ``flask.templating.render_template_string`` by default + (pull request ``#1515``). Version 0.10.2 -------------- diff --git a/docs/templating.rst b/docs/templating.rst index a8c8d0a9..11d5d48d 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -18,7 +18,10 @@ Jinja Setup Unless customized, Jinja2 is configured by Flask as follows: - autoescaping is enabled for all templates ending in ``.html``, - ``.htm``, ``.xml`` as well as ``.xhtml`` + ``.htm``, ``.xml`` as well as ``.xhtml`` when using + :func:`~flask.templating.render_template`. +- autoescaping is enabled for all strings when using + :func:`~flask.templating.render_template_string`. - a template has the ability to opt in/out autoescaping with the ``{% autoescape %}`` tag. - Flask inserts a couple of global functions and helpers into the diff --git a/docs/upgrading.rst b/docs/upgrading.rst index b0460b38..fca4d75b 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -37,6 +37,10 @@ Now the inheritance hierarchy takes precedence and handlers for more specific exception classes are executed instead of more general ones. See :ref:`error-handlers` for specifics. +The :func:`~flask.templating.render_template_string` function has changed to +autoescape template variables by default. This better matches the behavior +of :func:`~flask.templating.render_template`. + .. note:: There used to be a logic error allowing you to register handlers diff --git a/flask/app.py b/flask/app.py index dae6b24e..f0a8b69b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -724,12 +724,12 @@ class Flask(_PackageBoundObject): def select_jinja_autoescape(self, filename): """Returns ``True`` if autoescaping should be active for the given - template name. + template name. If no template name is given, returns `True`. .. versionadded:: 0.5 """ if filename is None: - return False + return True return filename.endswith(('.html', '.htm', '.xml', '.xhtml')) def update_template_context(self, context): diff --git a/flask/templating.py b/flask/templating.py index 59fd988e..8c95a6a7 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -127,7 +127,7 @@ def render_template(template_name_or_list, **context): def render_template_string(source, **context): """Renders a template from the given template source string - with the given context. + with the given context. Template variables will be autoescaped. :param source: the source code of the template to be rendered diff --git a/tests/templates/non_escaping_template.txt b/tests/templates/non_escaping_template.txt new file mode 100644 index 00000000..542864e8 --- /dev/null +++ b/tests/templates/non_escaping_template.txt @@ -0,0 +1,8 @@ +{{ text }} +{{ html }} +{% autoescape false %}{{ text }} +{{ html }}{% endautoescape %} +{% autoescape true %}{{ text }} +{{ html }}{% endautoescape %} +{{ text }} +{{ html }} diff --git a/tests/test_templating.py b/tests/test_templating.py index 293ca06f..b60a592a 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -81,10 +81,29 @@ def test_escaping(): ] def test_no_escaping(): + text = '

Hello World!' + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template('non_escaping_template.txt', text=text, + html=flask.Markup(text)) + lines = app.test_client().get('/').data.splitlines() + assert lines == [ + b'

Hello World!', + b'

Hello World!', + b'

Hello World!', + b'

Hello World!', + b'<p>Hello World!', + b'

Hello World!', + b'

Hello World!', + b'

Hello World!' + ] + +def test_escaping_without_template_filename(): app = flask.Flask(__name__) with app.test_request_context(): assert flask.render_template_string( - '{{ foo }}', foo='') == '' + '{{ foo }}', foo='') == '<test>' assert flask.render_template('mail.txt', foo='') == \ ' Mail' From 011b129b6bc615c7a24de626dd63b6a311fa6ce6 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 8 Jun 2015 16:31:35 -0500 Subject: [PATCH 030/440] Add kwarg to disable auto OPTIONS on add_url_rule Adds support for a kwarg `provide_automatic_options` on `add_url_rule`, which lets you turn off the automatic OPTIONS response on a per-URL basis even if your view functions are functions, not classes (so you can't provide attrs on them). --- flask/app.py | 6 ++++-- tests/test_basic.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index f0a8b69b..5b186577 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1013,8 +1013,10 @@ class Flask(_PackageBoundObject): # starting with Flask 0.8 the view_func object can disable and # force-enable the automatic options handling. - provide_automatic_options = getattr(view_func, - 'provide_automatic_options', None) + provide_automatic_options = options.pop( + 'provide_automatic_options', getattr(view_func, + 'provide_automatic_options', None)) + if provide_automatic_options is None: if 'OPTIONS' not in methods: diff --git a/tests/test_basic.py b/tests/test_basic.py index 695e346e..08e03aca 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1604,3 +1604,44 @@ def test_run_server_port(monkeypatch): hostname, port = 'localhost', 8000 app.run(hostname, port, debug=True) assert rv['result'] == 'running on %s:%s ...' % (hostname, port) + + +def test_disable_automatic_options(): + # Issue 1488: Add support for a kwarg to add_url_rule to disable the auto OPTIONS response + app = flask.Flask(__name__) + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule('/', 'index', index, provide_automatic_options=False) + app.add_url_rule('/more', 'more', more, methods=['GET', 'POST'], provide_automatic_options=False) + + c = app.test_client() + assert c.get('/').data == b'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD'] + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/') + else: + rv = c.open('/', method='OPTIONS') + assert rv.status_code == 405 + + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == b'POST' + assert c.get('/more').data == b'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/more') + else: + rv = c.open('/more', method='OPTIONS') + assert rv.status_code == 405 From a1360196447c8dad1b20fc611bd96210c0bfca28 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Tue, 7 Jul 2015 13:21:51 -0500 Subject: [PATCH 031/440] Updated changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index b33c3795..d1529526 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,9 @@ Version 1.0 - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). +- Added support for `provide_automatic_options` in `**kwargs` on + :meth:`add_url_rule` to turn off automatic OPTIONS when the `view_func` + argument is not a class (pull request ``#1489``). Version 0.10.2 -------------- From 8ba986e45e5b8f73f3f18da9d145b06722e5ceca Mon Sep 17 00:00:00 2001 From: Aayush Kasurde Date: Sun, 12 Jul 2015 21:08:16 +0530 Subject: [PATCH 032/440] Added fix for issue 1529 Signed-off-by: Aayush Kasurde --- examples/jqueryexample/jqueryexample.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/jqueryexample/jqueryexample.py b/examples/jqueryexample/jqueryexample.py index d1fca62b..39b81951 100644 --- a/examples/jqueryexample/jqueryexample.py +++ b/examples/jqueryexample/jqueryexample.py @@ -23,3 +23,6 @@ def add_numbers(): @app.route('/') def index(): return render_template('index.html') + +if __name__ == '__main__': + app.run() From 05c2e7c276ba14c8e8abce4dbb86e0d35c5268ba Mon Sep 17 00:00:00 2001 From: Wayne Ye Date: Tue, 14 Jul 2015 12:36:01 -0700 Subject: [PATCH 033/440] Fixed gevent introduction to use libev instead of libevent --- docs/deploying/wsgi-standalone.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 5bdd0483..bd7b1006 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -31,7 +31,7 @@ Gevent ------- `Gevent`_ is a coroutine-based Python networking library that uses -`greenlet`_ to provide a high-level synchronous API on top of `libevent`_ +`greenlet`_ to provide a high-level synchronous API on top of `libev`_ event loop:: from gevent.wsgi import WSGIServer @@ -42,7 +42,7 @@ event loop:: .. _Gevent: http://www.gevent.org/ .. _greenlet: http://greenlet.readthedocs.org/en/latest/ -.. _libevent: http://libevent.org/ +.. _libev: http://software.schmorp.de/pkg/libev.html Twisted Web ----------- From 93fe1d54bd44b190aa0f58990bc397bf22324452 Mon Sep 17 00:00:00 2001 From: Christian Becker Date: Thu, 16 Jul 2015 02:45:56 +0200 Subject: [PATCH 034/440] make external_url_handler example py3 compliant - a raises statement with multiple values is no longer allowed in python 3 --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 861b21ab..ad7b1507 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -219,7 +219,7 @@ def url_for(endpoint, **values): # Re-raise the BuildError, in context of original traceback. exc_type, exc_value, tb = sys.exc_info() if exc_value is error: - raise exc_type, exc_value, tb + raise exc_type(exc_value, endpoint, values).with_traceback(tb) else: raise error # url_for will use this result, instead of raising BuildError. From 5da31f8af36012a4f76c3bd0d536ae107e0519b5 Mon Sep 17 00:00:00 2001 From: Christian Becker Date: Wed, 15 Jul 2015 01:20:39 +0200 Subject: [PATCH 035/440] fix UnboundLocalError in handle_url_build_error - caused by changes in the execution model of python 3 where the alias of an except clause is cleared on exit of the except --- flask/app.py | 5 +++-- flask/testsuite/basic.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index cc3fc1d9..e073798a 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1631,8 +1631,9 @@ class Flask(_PackageBoundObject): rv = handler(error, endpoint, values) if rv is not None: return rv - except BuildError as error: - pass + except BuildError as e: + # make error available outside except block (py3) + error = e # At this point we want to reraise the exception. If the error is # still the same one we can reraise it with the original traceback, diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 31fade13..803a008c 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -772,6 +772,17 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_request_context(): self.assert_equal(flask.url_for('spam'), '/test_handler/') + def test_build_error_handler_reraise(self): + app = flask.Flask(__name__) + + # Test a custom handler which reraises the BuildError + def handler_raises_build_error(error, endpoint, values): + raise error + app.url_build_error_handlers.append(handler_raises_build_error) + + with app.test_request_context(): + self.assertRaises(BuildError, flask.url_for, 'not.existing') + def test_custom_converters(self): from werkzeug.routing import BaseConverter class ListConverter(BaseConverter): From 765f851a7c880fc654eada6aefb4ce7aa5b14525 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 16 Jul 2015 12:01:25 +0200 Subject: [PATCH 036/440] Changelog for #1533 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index a67937ff..89de874f 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,8 @@ Version 0.10.2 - Changed logic of before first request handlers to flip the flag after invoking. This will allow some uses that are potentially dangerous but should probably be permitted. +- Fixed Python 3 bug when a handler from `app.url_build_error_handlers` + reraises the `BuildError`. Version 0.10.1 -------------- From 128bc76af0f6c16ba71b41851be800ccdf354752 Mon Sep 17 00:00:00 2001 From: lobeck Date: Thu, 16 Jul 2015 13:53:59 +0200 Subject: [PATCH 037/440] Revert "make external_url_handler example py3 compliant" --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index ad7b1507..861b21ab 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -219,7 +219,7 @@ def url_for(endpoint, **values): # Re-raise the BuildError, in context of original traceback. exc_type, exc_value, tb = sys.exc_info() if exc_value is error: - raise exc_type(exc_value, endpoint, values).with_traceback(tb) + raise exc_type, exc_value, tb else: raise error # url_for will use this result, instead of raising BuildError. From c8f19f0afc8d49a68ec969097e6127d17706b6dc Mon Sep 17 00:00:00 2001 From: "Mathias J. Hennig" Date: Wed, 22 Jul 2015 23:35:13 +0200 Subject: [PATCH 038/440] Reimplement function with_metaclass() --- flask/_compat.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index 8ea7719f..fbbf914f 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -56,21 +56,13 @@ else: def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a # dummy metaclass for one level of class instantiation that replaces - # itself with the actual metaclass. Because of internal type checks - # we also need to make sure that we downgrade the custom metaclass - # for one level to something closer to type (that's why __call__ and - # __init__ comes back from type etc.). + # itself with the actual metaclass. # # This has the advantage over six.with_metaclass in that it does not # introduce dummy classes into the final MRO. - class metaclass(meta): - __call__ = type.__call__ - __init__ = type.__init__ - def __new__(cls, name, this_bases, d): - if this_bases is None: - return type.__new__(cls, name, (), d) - return meta(name, bases, d) - return metaclass('temporary_class', None, {}) + constructor = lambda c, name, b, dct: meta(name, bases, dct) + metaclass = type('with_metaclass', (type,), {'__new__': constructor}) + return type.__new__(metaclass, 'temporary_class', (object,), {}) # Certain versions of pypy have a bug where clearing the exception stack From b3767ae59f8d916333ebc2cc04d962fa33f76580 Mon Sep 17 00:00:00 2001 From: "Mathias J. Hennig" Date: Mon, 27 Jul 2015 15:32:23 +0200 Subject: [PATCH 039/440] Addressing feedback from pull request --- flask/_compat.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index fbbf914f..bfe607d6 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -54,15 +54,14 @@ else: def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a # dummy metaclass for one level of class instantiation that replaces # itself with the actual metaclass. - # - # This has the advantage over six.with_metaclass in that it does not - # introduce dummy classes into the final MRO. - constructor = lambda c, name, b, dct: meta(name, bases, dct) - metaclass = type('with_metaclass', (type,), {'__new__': constructor}) - return type.__new__(metaclass, 'temporary_class', (object,), {}) + class metaclass(type): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) # Certain versions of pypy have a bug where clearing the exception stack From 8b9cb6caa71c2db1a9c2ebd66f77ffe8b6306623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Gr=C3=B6ger?= Date: Sat, 22 Aug 2015 00:56:54 +0200 Subject: [PATCH 040/440] Update uwsgi/nginx deployment documentation Instead of using the uwsgi_modifier1 30 directive, the uwsgi docs recommend to use the mount / manage-script-name approch which mounts a module + callable to a certain path. This way, SCRIPT_NAME and PATH_INFO are correctly rewritten. See http://uwsgi-docs.readthedocs.org/en/latest/Nginx.html#hosting-multiple-apps-in-the-same-process-aka-managing-script-name-and-path-info Fixes #1464 --- docs/deploying/uwsgi.rst | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index f181d731..8ab985b9 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -29,36 +29,40 @@ Given a flask application in myapp.py, use the following command: .. sourcecode:: text - $ uwsgi -s /tmp/uwsgi.sock --module myapp --callable app + $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app Or, if you prefer: .. sourcecode:: text - $ uwsgi -s /tmp/uwsgi.sock -w myapp:app + $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app + +The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to +uwsgi, since its smarter about that. It is used together with the ``--mount`` +directive which will make requests to ``/yourapplication`` be directed to +``myapp:app``, where ``myapp`` refers to the name of the file of your flask +application (without extension). ``app`` is the callable inside of your +application (usually the line reads ``app = Flask(__name__)``. Configuring nginx ----------------- -A basic flask uWSGI configuration for nginx looks like this:: +A basic flask nginx configuration looks like this:: location = /yourapplication { rewrite ^ /yourapplication/; } location /yourapplication { try_files $uri @yourapplication; } location @yourapplication { include uwsgi_params; - uwsgi_param SCRIPT_NAME /yourapplication; - uwsgi_modifier1 30; - uwsgi_pass unix:/tmp/uwsgi.sock; + uwsgi_pass unix:/tmp/yourapplication.sock; } This configuration binds the application to ``/yourapplication``. If you want -to have it in the URL root it's a bit simpler because you don't have to tell -it the WSGI ``SCRIPT_NAME`` or set the uwsgi modifier to make use of it:: +to have it in the URL root its a bit simpler:: location / { try_files $uri @yourapplication; } location @yourapplication { include uwsgi_params; - uwsgi_pass unix:/tmp/uwsgi.sock; + uwsgi_pass unix:/tmp/yourapplication.sock; } .. _nginx: http://nginx.org/ From b644e2747f82fe662f03a4973490acaaf6476d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Gr=C3=B6ger?= Date: Sat, 22 Aug 2015 01:05:45 +0200 Subject: [PATCH 041/440] Mention virtual environments in uwsgi/nginx deployment docs --- docs/deploying/uwsgi.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 8ab985b9..2d15440f 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -44,6 +44,11 @@ directive which will make requests to ``/yourapplication`` be directed to application (without extension). ``app`` is the callable inside of your application (usually the line reads ``app = Flask(__name__)``. +If you want to deploy your flask application inside of a virtual environment, +you need to also add ``--virtualenv /path/to/virtual/environment``. You might +also need to add ``--plugin python`` or ``--plugin python3`` depending on which +python version you use for your project. + Configuring nginx ----------------- From 808bf6d204d426c26461e5c16b8983eb0345b037 Mon Sep 17 00:00:00 2001 From: msiyaj Date: Thu, 27 Aug 2015 00:27:44 -0400 Subject: [PATCH 042/440] Missing apostophe. --- docs/patterns/sqlalchemy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index cdf663a6..40e048e0 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -33,7 +33,7 @@ SQLAlchemy. It allows you to define tables and models in one go, similar to how Django works. In addition to the following text I recommend the official documentation on the `declarative`_ extension. -Here the example :file:`database.py` module for your application:: +Here's the example :file:`database.py` module for your application:: from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker From 5505827a4bd759a24696258983ec14c768358af8 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 14 Sep 2015 13:25:52 -0500 Subject: [PATCH 043/440] provide_automatic_options as explicit arg In add_url_rule, break provide_automatic_options out to an explicit kwarg, and add notes to the docstring. --- flask/app.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/flask/app.py b/flask/app.py index e03ff221..de147e9e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -944,7 +944,8 @@ class Flask(_PackageBoundObject): return iter(self._blueprint_order) @setupmethod - def add_url_rule(self, rule, endpoint=None, view_func=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. @@ -984,6 +985,10 @@ class Flask(_PackageBoundObject): endpoint :param view_func: the function to call when serving a request to the provided endpoint + :param provide_automatic_options: controls whether ``OPTIONS`` should + be provided automatically. If this + is not set, will check attributes on + the view or list of methods. :param options: the options to be forwarded to the underlying :class:`~werkzeug.routing.Rule` object. A change to Werkzeug is handling of method options. methods @@ -1013,10 +1018,9 @@ class Flask(_PackageBoundObject): # starting with Flask 0.8 the view_func object can disable and # force-enable the automatic options handling. - provide_automatic_options = options.pop( - 'provide_automatic_options', getattr(view_func, - 'provide_automatic_options', None)) - + if provide_automatic_options is None: + provide_automatic_options = getattr(view_func, + 'provide_automatic_options', None) if provide_automatic_options is None: if 'OPTIONS' not in methods: From a23ce4697155ae236c139c96c6d9dd65207a0cb6 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 14 Sep 2015 13:29:16 -0500 Subject: [PATCH 044/440] Update change log --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index cac78f73..b459fc1d 100644 --- a/CHANGES +++ b/CHANGES @@ -70,9 +70,9 @@ Version 1.0 - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). -- Added support for `provide_automatic_options` in `**kwargs` on - :meth:`add_url_rule` to turn off automatic OPTIONS when the `view_func` - argument is not a class (pull request ``#1489``). +- Added support for `provide_automatic_options` in :meth:`add_url_rule` to + turn off automatic OPTIONS when the `view_func` argument is not a class + (pull request ``#1489``). Version 0.10.2 -------------- From ee7f4b2babb7a831d515bd9cb1160d86dad6a97b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 1 Oct 2015 13:58:48 +0200 Subject: [PATCH 045/440] Added lineart logo --- artwork/logo-lineart.svg | 165 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 artwork/logo-lineart.svg diff --git a/artwork/logo-lineart.svg b/artwork/logo-lineart.svg new file mode 100644 index 00000000..615260dc --- /dev/null +++ b/artwork/logo-lineart.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + From 6cd191db4fd2a1f2593e0d120001073781ba698f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 1 Oct 2015 13:59:05 +0200 Subject: [PATCH 046/440] Ignore build folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5c012aaa..8cba18ff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ env env* dist +build *.egg *.egg-info _mailinglist From 1ac4156016a578fdf439ac69bc39b9c6687ea9f9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 2 Oct 2015 23:19:54 +0200 Subject: [PATCH 047/440] Fixed some lint warnings --- flask/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 186e6e29..2d24d8b2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -14,7 +14,7 @@ from threading import Lock from datetime import timedelta from itertools import chain from functools import update_wrapper -from collections import Mapping, deque +from collections import deque from werkzeug.datastructures import ImmutableDict from werkzeug.routing import Map, Rule, RequestRedirect, BuildError @@ -33,7 +33,7 @@ from .templating import DispatchingJinjaLoader, Environment, \ _default_template_ctx_processor from .signals import request_started, request_finished, got_request_exception, \ request_tearing_down, appcontext_tearing_down -from ._compat import reraise, string_types, text_type, integer_types, iterkeys +from ._compat import reraise, string_types, text_type, integer_types # a lock used for logger initialization _logger_lock = Lock() From 15f267e1ee401d317793ac71482d269588d22ff1 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Tue, 20 Oct 2015 14:40:55 +0530 Subject: [PATCH 048/440] Updated documentation for Setuptools Updated documentation by removing references of distribute and adding references of setuptools. Signed-off-by: Abhijeet Kasurde --- docs/patterns/distribute.rst | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index 997ca89b..9f8d980b 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -1,11 +1,11 @@ .. _distribute-deployment: -Deploying with Distribute +Deploying with Setuptools ========================= -`distribute`_, formerly setuptools, is an extension library that is -commonly used to (like the name says) distribute Python libraries and -extensions. It extends distutils, a basic module installation system +`Setuptools`_, is an extension library that is commonly used to +(like the name says) distribute Python libraries and +extensions. It extends distutils, a basic module installation system shipped with Python to also support various more complex constructs that make larger applications easier to distribute: @@ -28,10 +28,9 @@ are distributed with either distribute, the older setuptools or distutils. In this case we assume your application is called :file:`yourapplication.py` and you are not using a module, but a :ref:`package -`. Distributing resources with standard modules is -not supported by `distribute`_ so we will not bother with it. If you have -not yet converted your application into a package, head over to the -:ref:`larger-applications` pattern to see how this can be done. +`. If you have not yet converted your application into +a package, head over to the :ref:`larger-applications` pattern to see +how this can be done. A working deployment with distribute is the first step into more complex and more automated deployment scenarios. If you want to fully automate @@ -40,9 +39,9 @@ the process, also read the :ref:`fabric-deployment` chapter. Basic Setup Script ------------------ -Because you have Flask running, you either have setuptools or distribute -available on your system anyways. If you do not, fear not, there is a -script to install it for you: `distribute_setup.py`_. Just download and +Because you have Flask running, you have setuptools available on your system anyways. +Flask already depends upon setuptools. If you do not, fear not, there is a +script to install it for you: `ez_setup.py`_. Just download and run with your Python interpreter. Standard disclaimer applies: :ref:`you better use a virtualenv @@ -52,10 +51,6 @@ Your setup code always goes into a file named :file:`setup.py` next to your application. The name of the file is only convention, but because everybody will look for a file with that name, you better not change it. -Yes, even if you are using `distribute`, you are importing from a package -called `setuptools`. `distribute` is fully backwards compatible with -`setuptools`, so it also uses the same import name. - A basic :file:`setup.py` file for a Flask application looks like this:: from setuptools import setup @@ -127,7 +122,7 @@ requirements. Here some examples:: 'BrokenPackage>=0.7,<=1.0' ] -I mentioned earlier that dependencies are pulled from PyPI. What if you +As mentioned earlier that dependencies are pulled from PyPI. What if you want to depend on a package that cannot be found on PyPI and won't be because it is an internal package you don't want to share with anyone? Just still do as if there was a PyPI entry for it and provide a list of @@ -161,6 +156,6 @@ folder instead of copying the data over. You can then continue to work on the code without having to run `install` again after each change. -.. _distribute: https://pypi.python.org/pypi/distribute .. _pip: https://pypi.python.org/pypi/pip -.. _distribute_setup.py: http://python-distribute.org/distribute_setup.py +.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py +.. _Setuptools: https://pythonhosted.org/setuptools From 6102d210a3559577bdc0d90eaed87f58beb41e94 Mon Sep 17 00:00:00 2001 From: hidavy Date: Fri, 23 Oct 2015 16:20:02 +0800 Subject: [PATCH 049/440] Update CHANGES --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1727a794..e3d9b0eb 100644 --- a/CHANGES +++ b/CHANGES @@ -112,7 +112,7 @@ Version 0.10.1 Version 0.10 ------------ -Released on June 13nd 2013, codename Limoncello. +Released on June 13th 2013, codename Limoncello. - Changed default cookie serialization format from pickle to JSON to limit the impact an attacker can do if the secret key leaks. See From d526932a09557be4aff6d27261cabb7c5c5ebb8d Mon Sep 17 00:00:00 2001 From: Timo Furrer Date: Sat, 24 Oct 2015 07:04:23 +0200 Subject: [PATCH 050/440] support timedelta for SEND_FILE_MAX_AGE_DEFAULT config variable --- docs/config.rst | 3 ++- flask/app.py | 12 +++++++++++- flask/helpers.py | 14 +++++++++++++- flask/sessions.py | 5 +---- tests/test_config.py | 9 +++++++++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index fb39b4c4..855136ff 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -130,7 +130,8 @@ The following configuration values are used internally by Flask: ``SEND_FILE_MAX_AGE_DEFAULT`` Default cache control max age to use with :meth:`~flask.Flask.send_static_file` (the default static file handler) and - :func:`~flask.send_file`, in + :func:`~flask.send_file`, as + :class:`datetime.timedelta` or as seconds. seconds. Override this value on a per-file basis using the :meth:`~flask.Flask.get_send_file_max_age` diff --git a/flask/app.py b/flask/app.py index 2d24d8b2..3d741ae9 100644 --- a/flask/app.py +++ b/flask/app.py @@ -246,6 +246,16 @@ class Flask(_PackageBoundObject): permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME', get_converter=_make_timedelta) + #: A :class:`~datetime.timedelta` which is used as default cache_timeout + #: for the :func:`send_file` functions. The default is 12 hours. + #: + #: This attribute can also be configured from the config with the + #: ``SEND_FILE_MAX_AGE_DEFAULT`` configuration key. This configuration + #: variable can also be set with an integer value used as seconds. + #: Defaults to ``timedelta(hours=12)`` + send_file_max_age_default = ConfigAttribute('SEND_FILE_MAX_AGE_DEFAULT', + get_converter=_make_timedelta) + #: Enable this if you want to use the X-Sendfile feature. Keep in #: mind that the server has to support this. This only affects files #: sent with the :func:`send_file` method. @@ -297,7 +307,7 @@ class Flask(_PackageBoundObject): 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, - 'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours + 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, diff --git a/flask/helpers.py b/flask/helpers.py index 861b21ab..479ca4bb 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,6 +14,7 @@ import sys import pkgutil import posixpath import mimetypes +from datetime import timedelta from time import time from zlib import adler32 from threading import RLock @@ -856,7 +857,7 @@ class _PackageBoundObject(object): .. versionadded:: 0.9 """ - return current_app.config['SEND_FILE_MAX_AGE_DEFAULT'] + return total_seconds(current_app.send_file_max_age_default) def send_static_file(self, filename): """Function used internally to send static files from the static @@ -898,3 +899,14 @@ class _PackageBoundObject(object): if mode not in ('r', 'rb'): raise ValueError('Resources can only be opened for reading') return open(os.path.join(self.root_path, resource), mode) + + +def total_seconds(td): + """Returns the total seconds from a timedelta object. + + :param timedelta td: the timedelta to be converted in seconds + + :returns: number of seconds + :rtype: int + """ + return td.days * 60 * 60 * 24 + td.seconds diff --git a/flask/sessions.py b/flask/sessions.py index 1aa42f17..48fd08fa 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -17,14 +17,11 @@ from werkzeug.http import http_date, parse_date from werkzeug.datastructures import CallbackDict from . import Markup, json from ._compat import iteritems, text_type +from .helpers import total_seconds from itsdangerous import URLSafeTimedSerializer, BadSignature -def total_seconds(td): - return td.days * 60 * 60 * 24 + td.seconds - - class SessionMixin(object): """Expands a basic dictionary with an accessors that are expected by Flask extensions and users for the session. diff --git a/tests/test_config.py b/tests/test_config.py index c16bb2f9..7a17b607 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,6 +10,7 @@ import pytest import os +from datetime import timedelta import flask @@ -168,6 +169,14 @@ def test_session_lifetime(): assert app.permanent_session_lifetime.seconds == 42 +def test_send_file_max_age(): + app = flask.Flask(__name__) + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600 + assert app.send_file_max_age_default.seconds == 3600 + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(hours=2) + assert app.send_file_max_age_default.seconds == 7200 + + def test_get_namespace(): app = flask.Flask(__name__) app.config['FOO_OPTION_1'] = 'foo option 1' From b8dc6c12b76cd96d38127328296fa79956fcbf18 Mon Sep 17 00:00:00 2001 From: Timo Furrer Date: Sat, 24 Oct 2015 09:14:01 +0200 Subject: [PATCH 051/440] add python 3.5 build to travis config --- .travis.yml | 3 +++ tox.ini | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b9a4eb25..8d2d3c60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ python: - "pypy" - "3.3" - "3.4" + - "3.5" env: - REQUIREMENTS=lowest @@ -20,6 +21,8 @@ matrix: env: REQUIREMENTS=lowest - python: "3.4" env: REQUIREMENTS=lowest + - python: "3.5" + env: REQUIREMENTS=lowest install: diff --git a/tox.ini b/tox.ini index 3e170d87..83aed62f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py26,py27,pypy}-{lowest,release,devel}, {py33,py34}-{release,devel} +envlist = {py26,py27,pypy}-{lowest,release,devel}, {py33,py34,py35}-{release,devel} [testenv] commands = From 99bc0dfd3bc866a67e6d56a62f0edbf773dcb5b6 Mon Sep 17 00:00:00 2001 From: Michael Klich Date: Thu, 29 Oct 2015 11:24:39 +0000 Subject: [PATCH 052/440] Add info about working with VE and Python 3 Python 3 does not have execfile function. --- docs/deploying/mod_wsgi.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index 41b82c2b..c1ce170a 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -193,5 +193,11 @@ Add the following lines to the top of your ``.wsgi`` file:: activate_this = '/path/to/env/bin/activate_this.py' execfile(activate_this, dict(__file__=activate_this)) +For Python 3 add the following lines to the top of your ``.wsgi`` file:: + + activate_this = '/path/to/env/bin/activate_this.py' + with open(activate_this) as file_: + exec(file_.read(), dict(__file__=activate_this)) + This sets up the load paths according to the settings of the virtual environment. Keep in mind that the path has to be absolute. From 41622c8d681a170f39df2ab8dff170d3b8d2d139 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 31 Oct 2015 15:17:45 -0700 Subject: [PATCH 053/440] Link article about deploying on Azure/IIS --- docs/deploying/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 272a9e27..ba506d6f 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -24,7 +24,7 @@ Hosted options - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ - `Sharing your Localhost Server with Localtunnel `_ - +- `Deploying on Azure (IIS) `_ Self-hosted options ------------------- From 4a8f3eacbb94f4db2d85b96dc64df6ee476452db Mon Sep 17 00:00:00 2001 From: wodim Date: Wed, 4 Nov 2015 12:00:44 +0100 Subject: [PATCH 054/440] Fix typo in templating.rst --- docs/templating.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index 11d5d48d..9ba2223a 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -132,7 +132,7 @@ characters in text, but can also lead to security problems. (see Sometimes however you will need to disable autoescaping in templates. This can be the case if you want to explicitly inject HTML into pages, for -example if they come from a system that generate secure HTML like a +example if they come from a system that generates secure HTML like a markdown to HTML converter. There are three ways to accomplish that: From 7f678aaf5a066ff71289743a64e5cd685b660428 Mon Sep 17 00:00:00 2001 From: Eric Yang Date: Wed, 4 Nov 2015 22:08:31 +0800 Subject: [PATCH 055/440] Fix a typo Maybe this is a typo? --- flask/exthook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/exthook.py b/flask/exthook.py index 9b822811..05ac4e35 100644 --- a/flask/exthook.py +++ b/flask/exthook.py @@ -111,7 +111,7 @@ class ExtensionImporter(object): if module_name == important_module: return True - # Some python versions will will clean up modules so early that the + # Some python versions will clean up modules so early that the # module name at that point is no longer set. Try guessing from # the filename then. filename = os.path.abspath(tb.tb_frame.f_code.co_filename) From 1d49343bb19933f5f8057a548cb69657d8cbd699 Mon Sep 17 00:00:00 2001 From: lord63 Date: Sat, 7 Nov 2015 09:04:24 +0800 Subject: [PATCH 056/440] Fix typo in app_ctx_globals_class doc in app.py --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 3d741ae9..91139773 100644 --- a/flask/app.py +++ b/flask/app.py @@ -168,7 +168,7 @@ class Flask(_PackageBoundObject): #: #: 1. Store arbitrary attributes on flask.g. #: 2. Add a property for lazy per-request database connectors. - #: 3. Return None instead of AttributeError on expected attributes. + #: 3. Return None instead of AttributeError on unexpected attributes. #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. #: #: In Flask 0.9 this property was called `request_globals_class` but it From 2faf245876822702e99d036d23cd3792ab5e0b84 Mon Sep 17 00:00:00 2001 From: Daniel Thul Date: Wed, 11 Nov 2015 23:46:51 +0100 Subject: [PATCH 057/440] Fix typo. --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 855136ff..245d3bab 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -132,7 +132,7 @@ The following configuration values are used internally by Flask: default static file handler) and :func:`~flask.send_file`, as :class:`datetime.timedelta` or as seconds. - seconds. Override this value on a per-file + Override this value on a per-file basis using the :meth:`~flask.Flask.get_send_file_max_age` hook on :class:`~flask.Flask` or From 51b62fb044ef59f0024ad890ff29765381ae1e29 Mon Sep 17 00:00:00 2001 From: lord63 Date: Sun, 22 Nov 2015 14:43:25 +0800 Subject: [PATCH 058/440] Render the code block in mongokit.rst --- docs/patterns/mongokit.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/patterns/mongokit.rst b/docs/patterns/mongokit.rst index b8a6ecfb..9d1b3e2a 100644 --- a/docs/patterns/mongokit.rst +++ b/docs/patterns/mongokit.rst @@ -48,6 +48,7 @@ insert query to the next without any problem. MongoKit is just schemaless too, but implements some validation to ensure data integrity. Here is an example document (put this also into :file:`app.py`, e.g.):: + from mongokit import ValidationError def max_length(length): From 4a576b06e8455d117910a8d10d006d136124f11f Mon Sep 17 00:00:00 2001 From: Redian Date: Thu, 26 Nov 2015 21:04:23 +0000 Subject: [PATCH 059/440] Add .cache dir to gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I think this could be handy and potentially minimise the risk anyone commits it to the project. ``` flask - (master) $ tree .cache/ .cache/ └── v └── cache └── lastfailed ``` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8cba18ff..5f6168f6 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build *.egg-info _mailinglist .tox +.cache/ From 96f7b07caf15a23a67e9e9dfd086d38e400b2db5 Mon Sep 17 00:00:00 2001 From: Redian Ibra Date: Thu, 26 Nov 2015 21:43:05 +0000 Subject: [PATCH 060/440] Skip test if redbaron is not installed --- scripts/test_import_migration.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/scripts/test_import_migration.py b/scripts/test_import_migration.py index 11d1f6a3..f000f2b1 100644 --- a/scripts/test_import_migration.py +++ b/scripts/test_import_migration.py @@ -2,30 +2,29 @@ # # Author: Keyan Pishdadian import pytest -from redbaron import RedBaron +redbaron = pytest.importorskip("redbaron") import flaskext_migrate as migrate - def test_simple_from_import(): - red = RedBaron("from flask.ext import foo") + red = redbaron.RedBaron("from flask.ext import foo") output = migrate.fix_tester(red) assert output == "import flask_foo as foo" def test_from_to_from_import(): - red = RedBaron("from flask.ext.foo import bar") + red = redbaron.RedBaron("from flask.ext.foo import bar") output = migrate.fix_tester(red) assert output == "from flask_foo import bar as bar" def test_multiple_import(): - red = RedBaron("from flask.ext.foo import bar, foobar, something") + red = redbaron.RedBaron("from flask.ext.foo import bar, foobar, something") output = migrate.fix_tester(red) assert output == "from flask_foo import bar, foobar, something" def test_multiline_import(): - red = RedBaron("from flask.ext.foo import \ + red = redbaron.RedBaron("from flask.ext.foo import \ bar,\ foobar,\ something") @@ -34,37 +33,37 @@ def test_multiline_import(): def test_module_import(): - red = RedBaron("import flask.ext.foo") + red = redbaron.RedBaron("import flask.ext.foo") output = migrate.fix_tester(red) assert output == "import flask_foo" def test_named_module_import(): - red = RedBaron("import flask.ext.foo as foobar") + red = redbaron.RedBaron("import flask.ext.foo as foobar") output = migrate.fix_tester(red) assert output == "import flask_foo as foobar" def test_named_from_import(): - red = RedBaron("from flask.ext.foo import bar as baz") + red = redbaron.RedBaron("from flask.ext.foo import bar as baz") output = migrate.fix_tester(red) assert output == "from flask_foo import bar as baz" def test_parens_import(): - red = RedBaron("from flask.ext.foo import (bar, foo, foobar)") + red = redbaron.RedBaron("from flask.ext.foo import (bar, foo, foobar)") output = migrate.fix_tester(red) assert output == "from flask_foo import (bar, foo, foobar)" def test_function_call_migration(): - red = RedBaron("flask.ext.foo(var)") + red = redbaron.RedBaron("flask.ext.foo(var)") output = migrate.fix_tester(red) assert output == "flask_foo(var)" def test_nested_function_call_migration(): - red = RedBaron("import flask.ext.foo\n\n" + red = redbaron.RedBaron("import flask.ext.foo\n\n" "flask.ext.foo.bar(var)") output = migrate.fix_tester(red) assert output == ("import flask_foo\n\n" @@ -72,6 +71,6 @@ def test_nested_function_call_migration(): def test_no_change_to_import(): - red = RedBaron("from flask import Flask") + red = redbaron.RedBaron("from flask import Flask") output = migrate.fix_tester(red) assert output == "from flask import Flask" From db468a46188c14df12d0e87259834ebea86ec2ea Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 1 Dec 2015 20:58:12 -0800 Subject: [PATCH 061/440] ignore pycharm config --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5f6168f6..9bf4f063 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ build _mailinglist .tox .cache/ +.idea/ From 25aab43f8e27e80e8f05498849c17e041791785b Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Thu, 3 Dec 2015 09:51:24 +0000 Subject: [PATCH 062/440] Fixed a typo I think 'app' is the intended word here, but I am not 100% sure. --- docs/patterns/appfactories.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index 87b9d46f..bcac210c 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -7,7 +7,7 @@ If you are already using packages and blueprints for your application (:ref:`blueprints`) there are a couple of really nice ways to further improve the experience. A common pattern is creating the application object when the blueprint is imported. But if you move the creation of this object, -into a function, you can then create multiple instances of this and later. +into a function, you can then create multiple instances of this app later. So why would you want to do this? From c388e9c24e835f9f4f91b6194d6b0997c8535a53 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 3 Dec 2015 16:18:46 -0800 Subject: [PATCH 063/440] add myself to authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 3cf4b2f2..b0a4b6f3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,3 +32,4 @@ Patches and Suggestions - Stephane Wirtel - Thomas Schranz - Zhao Xiaohong +- David Lord @davidism From d7b20e0ad7b99e05a327485ce9c150bf2f993b0e Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 3 Dec 2015 16:19:24 -0800 Subject: [PATCH 064/440] use https instead of git protocol git protocol is blocked on some networks --- tox.ini | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tox.ini b/tox.ini index 83aed62f..a138877d 100644 --- a/tox.ini +++ b/tox.ini @@ -15,13 +15,10 @@ deps= lowest: itsdangerous==0.21 lowest: blinker==1.0 release: blinker - devel: git+git://github.com/mitsuhiko/werkzeug.git - devel: git+git://github.com/mitsuhiko/jinja2.git - devel: git+git://github.com/mitsuhiko/itsdangerous.git - devel: git+git://github.com/jek/blinker.git - -# extra dependencies -git+git://github.com/jek/blinker.git#egg=blinker + devel: git+https://github.com/mitsuhiko/werkzeug.git + devel: git+https://github.com/mitsuhiko/jinja2.git + devel: git+https://github.com/mitsuhiko/itsdangerous.git + devel: git+https://github.com/jek/blinker.git [testenv:docs] deps = sphinx From 910ad019160730e8cb7ca58cec214a9c5ca296a4 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 3 Dec 2015 16:50:16 -0800 Subject: [PATCH 065/440] use https instead of git protocol git protocol is blocked on some networks --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index bf7b494f..3d7df149 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "docs/_themes"] path = docs/_themes - url = git://github.com/mitsuhiko/flask-sphinx-themes.git + url = https://github.com/mitsuhiko/flask-sphinx-themes.git From fcd573e120eaf4b97d05a3bfa225133741c04428 Mon Sep 17 00:00:00 2001 From: accraze Date: Wed, 9 Dec 2015 19:49:47 -0800 Subject: [PATCH 066/440] added note about directory permission syntax change docs had httpd 2.2 syntax for directory permission with a link to Apache 2.4 changes. added an example of httpd 2.4 syntax resolves #1644 --- docs/deploying/mod_wsgi.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index c1ce170a..9145b4f9 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -116,6 +116,20 @@ Note: There have been some changes in access control configuration for `Apache 2 .. _Apache 2.4: http://httpd.apache.org/docs/trunk/upgrading.html +Most notably, the syntax for directory permissions has changed from httpd 2.2 + +.. sourcecode:: apache + + Order allow,deny + Allow from all + +to httpd 2.4 syntax + +.. sourcecode:: apache + + Require all granted + + For more information consult the `mod_wsgi wiki`_. .. _mod_wsgi: http://code.google.com/p/modwsgi/ From b55bc0baa291fb3ac2f62f5852c2469f1dda9bdc Mon Sep 17 00:00:00 2001 From: lord63 Date: Sun, 13 Dec 2015 22:08:31 +0800 Subject: [PATCH 067/440] Remove with_statement in flask/ctx.py --- flask/ctx.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask/ctx.py b/flask/ctx.py index 91b5ee50..1e01df78 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import sys from functools import update_wrapper From 7368a164c7506046a4a0b0ce2113c2aaa7800087 Mon Sep 17 00:00:00 2001 From: lord63 Date: Sun, 20 Dec 2015 20:35:46 +0800 Subject: [PATCH 068/440] Clarify the python versions that flask supports --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index 52e2df64..490ac02e 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,13 @@ setup( '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', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ], From 23cf923c7c2e4a3808e6c71b6faa34d1749d4cb6 Mon Sep 17 00:00:00 2001 From: AvivC Date: Sun, 27 Dec 2015 04:55:57 +0200 Subject: [PATCH 069/440] Clarified CONTRIBUTING.rst Added 'cd flask' before 'pip install --editable .'. --- CONTRIBUTING.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 419c70cb..12083f52 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -49,6 +49,7 @@ Clone this repository:: Install Flask as an editable package using the current source:: + cd flask pip install --editable . Then you can run the testsuite with:: From 102a33ca528f34bae0cbbfc60cb63d5e2dab7cf2 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Wed, 30 Dec 2015 01:04:24 -0800 Subject: [PATCH 070/440] Update `tox` installation instructions to point to PyPI --- CONTRIBUTING.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 12083f52..99b75f69 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -62,11 +62,9 @@ on. Travis is set up to run the full testsuite when you submit your pull request anyways. If you really want to test everything, you will have to install ``tox`` instead -of ``pytest``. Currently we're depending on a development version of Tox -because the released version is missing features we absolutely need. You can -install it with:: +of ``pytest``. You can install it with:: - pip install hg+https://bitbucket.org/hpk42/tox + pip install tox The ``tox`` command will then run all tests against multiple combinations Python versions and dependency versions. From 952a6c8989137bb0c44adaf37df0227b8e426a75 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 2 Jan 2016 10:56:02 -0800 Subject: [PATCH 071/440] Werkzeug should not block propagated exceptions from Flask --- flask/app.py | 1 + flask/cli.py | 3 ++- tests/test_basic.py | 20 ++++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 91139773..f75dd818 100644 --- a/flask/app.py +++ b/flask/app.py @@ -833,6 +833,7 @@ class Flask(_PackageBoundObject): self.debug = bool(debug) options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) + options.setdefault('passthrough_errors', True) try: run_simple(host, port, self, **options) finally: diff --git a/flask/cli.py b/flask/cli.py index 360ce12e..141c9eea 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -422,7 +422,8 @@ def run_command(info, host, port, reload, debugger, eager_loading, print(' * Forcing debug %s' % (info.debug and 'on' or 'off')) run_simple(host, port, app, use_reloader=reload, - use_debugger=debugger, threaded=with_threads) + use_debugger=debugger, threaded=with_threads, + passthrough_errors=True) @click.command('shell', short_help='Runs a shell in the app context.') diff --git a/tests/test_basic.py b/tests/test_basic.py index d22e9e5b..d7cc7a3f 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1222,6 +1222,26 @@ def test_exception_propagation(): t.join() +@pytest.mark.parametrize('debug', [True, False]) +@pytest.mark.parametrize('use_debugger', [True, False]) +@pytest.mark.parametrize('use_reloader', [True, False]) +@pytest.mark.parametrize('propagate_exceptions', [None, True, False]) +def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger, + use_reloader, propagate_exceptions): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(*args, **kwargs): + rv['passthrough_errors'] = kwargs.get('passthrough_errors') + + app = flask.Flask(__name__) + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions + app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) + # make sure werkzeug always passes errors through + assert rv['passthrough_errors'] + + def test_max_content_length(): app = flask.Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 64 From 826d7475cdab43b85d79f99e8eae5dabb01baf7b Mon Sep 17 00:00:00 2001 From: Aviv Cohn Date: Tue, 5 Jan 2016 01:10:35 +0200 Subject: [PATCH 072/440] Clarified the docstring in method Flask.preprocess_request. The doc now clearly states that the method invokes two set of hook functions, and how these are filtered before execution. --- flask/app.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/flask/app.py b/flask/app.py index 91139773..33b543dd 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1791,16 +1791,18 @@ class Flask(_PackageBoundObject): raise error def preprocess_request(self): - """Called before the actual request dispatching and will - call each :meth:`before_request` decorated function, passing no - arguments. - If any of these functions returns a value, it's handled as - if it was the return value from the view and further - request handling is stopped. + """Called before the request dispatching. - This also triggers the :meth:`url_value_processor` functions before - the actual :meth:`before_request` functions are called. + Triggers two set of hook functions that should be invoked prior to request dispatching: + :attr:`url_value_preprocessors` and :attr:`before_request_funcs` + (the latter are functions decorated with :meth:`before_request` decorator). + In both cases, the method triggers only the functions that are either global + or registered to the current blueprint. + + If any function in :attr:`before_request_funcs` returns a value, it's handled as if it was + the return value from the view function, and further request handling is stopped. """ + bp = _request_ctx_stack.top.request.blueprint funcs = self.url_value_preprocessors.get(None, ()) From edb65cc0f056bdfb201531066976e1cb8a90ad1f Mon Sep 17 00:00:00 2001 From: nivm Date: Sun, 10 Jan 2016 12:33:35 +0200 Subject: [PATCH 073/440] remove 'flask' from etags to obscure server technologies --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 479ca4bb..3210772d 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -540,7 +540,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if add_etags and filename is not None: try: - rv.set_etag('flask-%s-%s-%s' % ( + rv.set_etag('%s-%s-%s' % ( os.path.getmtime(filename), os.path.getsize(filename), adler32( From daceb3e3a028b4b408c4bbdbdef0047f1de3a7c9 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Wed, 30 Dec 2015 03:11:53 -0800 Subject: [PATCH 074/440] Add support for serializing top-level arrays to JSON Fix #170, #248, #510, #673, #1177 --- AUTHORS | 3 +- CHANGES | 3 ++ docs/security.rst | 87 ++++---------------------------------- flask/json.py | 40 ++++++++++++++---- tests/test_helpers.py | 98 ++++++++++++++++++++++++++++++------------- 5 files changed, 115 insertions(+), 116 deletions(-) diff --git a/AUTHORS b/AUTHORS index b0a4b6f3..d081d9f8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,9 +15,11 @@ Patches and Suggestions - Chris Grindstaff - Christopher Grebs - Daniel Neuhäuser +- David Lord @davidism - Edmond Burnett - Florent Xicluna - Georg Brandl +- Jeff Widman @jeffwidman - Justin Quick - Kenneth Reitz - Keyan Pishdadian @@ -32,4 +34,3 @@ Patches and Suggestions - Stephane Wirtel - Thomas Schranz - Zhao Xiaohong -- David Lord @davidism diff --git a/CHANGES b/CHANGES index e3d9b0eb..41b054d7 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Version 1.0 (release date to be announced, codename to be selected) +- Added support to serializing top-level arrays to :func:`flask.jsonify`. This + introduces a security risk in ancient browsers. See + :ref:`json_security` for details. - Added before_render_template signal. - Added `**kwargs` to :meth:`flask.Test.test_client` to support passing additional keyword arguments to the constructor of diff --git a/docs/security.rst b/docs/security.rst index 55ac91fe..3e97834d 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -95,81 +95,12 @@ the form validation framework, which does not exist in Flask. JSON Security ------------- -.. admonition:: ECMAScript 5 Changes - - Starting with ECMAScript 5 the behavior of literals changed. Now they - are not constructed with the constructor of ``Array`` and others, but - with the builtin constructor of ``Array`` which closes this particular - attack vector. - -JSON itself is a high-level serialization format, so there is barely -anything that could cause security problems, right? You can't declare -recursive structures that could cause problems and the only thing that -could possibly break are very large responses that can cause some kind of -denial of service at the receiver's side. - -However there is a catch. Due to how browsers work the CSRF issue comes -up with JSON unfortunately. Fortunately there is also a weird part of the -JavaScript specification that can be used to solve that problem easily and -Flask is kinda doing that for you by preventing you from doing dangerous -stuff. Unfortunately that protection is only there for -:func:`~flask.jsonify` so you are still at risk when using other ways to -generate JSON. - -So what is the issue and how to avoid it? The problem are arrays at -top-level in JSON. Imagine you send the following data out in a JSON -request. Say that's exporting the names and email addresses of all your -friends for a part of the user interface that is written in JavaScript. -Not very uncommon: - -.. sourcecode:: javascript - - [ - {"username": "admin", - "email": "admin@localhost"} - ] - -And it is doing that of course only as long as you are logged in and only -for you. And it is doing that for all ``GET`` requests to a certain URL, -say the URL for that request is -``http://example.com/api/get_friends.json``. - -So now what happens if a clever hacker is embedding this to his website -and social engineers a victim to visiting his site: - -.. sourcecode:: html - - - - - -If you know a bit of JavaScript internals you might know that it's -possible to patch constructors and register callbacks for setters. An -attacker can use this (like above) to get all the data you exported in -your JSON file. The browser will totally ignore the :mimetype:`application/json` -mimetype if :mimetype:`text/javascript` is defined as content type in the script -tag and evaluate that as JavaScript. Because top-level array elements are -allowed (albeit useless) and we hooked in our own constructor, after that -page loaded the data from the JSON response is in the `captured` array. - -Because it is a syntax error in JavaScript to have an object literal -(``{...}``) toplevel an attacker could not just do a request to an -external URL with the script tag to load up the data. So what Flask does -is to only allow objects as toplevel elements when using -:func:`~flask.jsonify`. Make sure to do the same when using an ordinary -JSON generate function. +In Flask 0.10 and lower, :func:`~flask.jsonify` did not serialize top-level +arrays to JSON. This was because of a security vulnerability in ECMAScript 4. + +ECMAScript 5 closed this vulnerability, so only extremely old browsers are +still vulnerable. All of these browsers have `other more serious +vulnerabilities +`_, so +this behavior was changed and :func:`~flask.jsonify` now supports serializing +arrays. diff --git a/flask/json.py b/flask/json.py index 885214d3..9bcaf72c 100644 --- a/flask/json.py +++ b/flask/json.py @@ -199,10 +199,22 @@ def htmlsafe_dump(obj, fp, **kwargs): def jsonify(*args, **kwargs): - """Creates a :class:`~flask.Response` with the JSON representation of - the given arguments with an :mimetype:`application/json` mimetype. The - arguments to this function are the same as to the :class:`dict` - constructor. + """This function wraps :func:`dumps` to add a few enhancements that make + life easier. It turns the JSON output into a :class:`~flask.Response` + object with the :mimetype:`application/json` mimetype. For convenience, it + also converts multiple arguments into an array or multiple keyword arguments + into a dict. This means that both ``jsonify(1,2,3)`` and + ``jsonify([1,2,3])`` serialize to ``[1,2,3]``. + + For clarity, the JSON serialization behavior has the following differences + from :func:`dumps`: + + 1. Single argument: Passed straight through to :func:`dumps`. + 2. Multiple arguments: Converted to an array before being passed to + :func:`dumps`. + 3. Multiple keyword arguments: Converted to a dict before being passed to + :func:`dumps`. + 4. Both args and kwargs: Behavior undefined and will throw an exception. Example usage:: @@ -222,8 +234,10 @@ def jsonify(*args, **kwargs): "id": 42 } - For security reasons only objects are supported toplevel. For more - information about this, have a look at :ref:`json-security`. + + .. versionchanged:: 1.0 + Added support for serializing top-level arrays. This introduces a + security risk in ancient browsers. See :ref:`json_security` for details. This function's response will be pretty printed if it was not requested with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless @@ -242,11 +256,21 @@ def jsonify(*args, **kwargs): indent = 2 separators = (', ', ': ') + if args and kwargs: + raise TypeError( + "jsonify() behavior undefined when passed both args and kwargs" + ) + elif len(args) == 1: # single args are passed directly to dumps() + data = args[0] + elif args: # convert multiple args into an array + data = list(args) + else: # convert kwargs to a dict + data = dict(kwargs) + # Note that we add '\n' to end of response # (see https://github.com/mitsuhiko/flask/pull/1262) rv = current_app.response_class( - (dumps(dict(*args, **kwargs), indent=indent, separators=separators), - '\n'), + (dumps(data, indent=indent, separators=separators), '\n'), mimetype='application/json') return rv diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8d09327f..620fd792 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -31,24 +31,6 @@ def has_encoding(name): class TestJSON(object): - def test_jsonify_date_types(self): - """Test jsonify with datetime.date and datetime.datetime types.""" - - test_dates = ( - datetime.datetime(1973, 3, 11, 6, 30, 45), - datetime.date(1975, 1, 5) - ) - - app = flask.Flask(__name__) - c = app.test_client() - - for i, d in enumerate(test_dates): - url = '/datetest{0}'.format(i) - app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val)) - rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) - def test_post_empty_json_adds_exception_to_response_content_in_debug(self): app = flask.Flask(__name__) app.config['DEBUG'] = True @@ -103,8 +85,41 @@ class TestJSON(object): content_type='application/json; charset=iso-8859-15') assert resp.data == u'Hällo Wörld'.encode('utf-8') - def test_jsonify(self): - d = dict(a=23, b=42, c=[1, 2, 3]) + def test_json_as_unicode(self): + app = flask.Flask(__name__) + + app.config['JSON_AS_ASCII'] = True + with app.app_context(): + rv = flask.json.dumps(u'\N{SNOWMAN}') + assert rv == '"\\u2603"' + + app.config['JSON_AS_ASCII'] = False + with app.app_context(): + rv = flask.json.dumps(u'\N{SNOWMAN}') + assert rv == u'"\u2603"' + + def test_jsonify_basic_types(self): + """Test jsonify with basic types.""" + # Should be able to use pytest parametrize on this, but I couldn't + # figure out the correct syntax + # https://pytest.org/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions + test_data = (0, 1, 23, 3.14, 's', "longer string", True, False,) + app = flask.Flask(__name__) + c = app.test_client() + for i, d in enumerate(test_data): + url = '/jsonify_basic_types{0}'.format(i) + app.add_url_rule(url, str(i), lambda x=d: flask.jsonify(x)) + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == d + + def test_jsonify_dicts(self): + """Test jsonify with dicts and kwargs unpacking.""" + d = dict( + a=0, b=23, c=3.14, d='t', e='Hi', f=True, g=False, + h=['test list', 10, False], + i={'test':'dict'} + ) app = flask.Flask(__name__) @app.route('/kw') def return_kwargs(): @@ -118,18 +133,43 @@ class TestJSON(object): assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data) == d - def test_json_as_unicode(self): + def test_jsonify_arrays(self): + """Test jsonify of lists and args unpacking.""" + l = [ + 0, 42, 3.14, 't', 'hello', True, False, + ['test list', 2, False], + {'test':'dict'} + ] app = flask.Flask(__name__) + @app.route('/args_unpack') + def return_args_unpack(): + return flask.jsonify(*l) + @app.route('/array') + def return_array(): + return flask.jsonify(l) + c = app.test_client() + for url in '/args_unpack', '/array': + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == l - app.config['JSON_AS_ASCII'] = True - with app.app_context(): - rv = flask.json.dumps(u'\N{SNOWMAN}') - assert rv == '"\\u2603"' + def test_jsonify_date_types(self): + """Test jsonify with datetime.date and datetime.datetime types.""" - app.config['JSON_AS_ASCII'] = False - with app.app_context(): - rv = flask.json.dumps(u'\N{SNOWMAN}') - assert rv == u'"\u2603"' + test_dates = ( + datetime.datetime(1973, 3, 11, 6, 30, 45), + datetime.date(1975, 1, 5) + ) + + app = flask.Flask(__name__) + c = app.test_client() + + for i, d in enumerate(test_dates): + url = '/datetest{0}'.format(i) + app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val)) + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) def test_json_attr(self): app = flask.Flask(__name__) From 0edf0a0e3a8b0f3e8309822044631fe7864d3066 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Mon, 25 Jan 2016 15:02:27 -0800 Subject: [PATCH 075/440] Cleanup jsonify() function Cleanup some leftover stuff from #1671. PEP8 spacing, args/kwargs don't need to be converted to list/dict, and Sphinx formatting. --- CHANGES | 2 +- flask/json.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 41b054d7..0337ec47 100644 --- a/CHANGES +++ b/CHANGES @@ -10,7 +10,7 @@ Version 1.0 - Added support to serializing top-level arrays to :func:`flask.jsonify`. This introduces a security risk in ancient browsers. See - :ref:`json_security` for details. + :ref:`json-security` for details. - Added before_render_template signal. - Added `**kwargs` to :meth:`flask.Test.test_client` to support passing additional keyword arguments to the constructor of diff --git a/flask/json.py b/flask/json.py index 9bcaf72c..a8eb5922 100644 --- a/flask/json.py +++ b/flask/json.py @@ -211,9 +211,9 @@ def jsonify(*args, **kwargs): 1. Single argument: Passed straight through to :func:`dumps`. 2. Multiple arguments: Converted to an array before being passed to - :func:`dumps`. + :func:`dumps`. 3. Multiple keyword arguments: Converted to a dict before being passed to - :func:`dumps`. + :func:`dumps`. 4. Both args and kwargs: Behavior undefined and will throw an exception. Example usage:: @@ -237,7 +237,7 @@ def jsonify(*args, **kwargs): .. versionchanged:: 1.0 Added support for serializing top-level arrays. This introduces a - security risk in ancient browsers. See :ref:`json_security` for details. + security risk in ancient browsers. See :ref:`json-security` for details. This function's response will be pretty printed if it was not requested with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless @@ -260,12 +260,10 @@ def jsonify(*args, **kwargs): raise TypeError( "jsonify() behavior undefined when passed both args and kwargs" ) - elif len(args) == 1: # single args are passed directly to dumps() + elif len(args) == 1: # single args are passed directly to dumps() data = args[0] - elif args: # convert multiple args into an array - data = list(args) - else: # convert kwargs to a dict - data = dict(kwargs) + else: + data = args or kwargs # Note that we add '\n' to end of response # (see https://github.com/mitsuhiko/flask/pull/1262) From 992d9be96ef0f0d746bd4a77b9081bff3b44524b Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 25 Jan 2016 22:56:51 -0800 Subject: [PATCH 076/440] clean up --- flask/json.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/flask/json.py b/flask/json.py index a8eb5922..cfc28c53 100644 --- a/flask/json.py +++ b/flask/json.py @@ -195,7 +195,7 @@ def htmlsafe_dumps(obj, **kwargs): def htmlsafe_dump(obj, fp, **kwargs): """Like :func:`htmlsafe_dumps` but writes into a file object.""" - fp.write(unicode(htmlsafe_dumps(obj, **kwargs))) + fp.write(text_type(htmlsafe_dumps(obj, **kwargs))) def jsonify(*args, **kwargs): @@ -251,26 +251,21 @@ def jsonify(*args, **kwargs): indent = None separators = (',', ':') - if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] \ - and not request.is_xhr: + if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_xhr: indent = 2 separators = (', ', ': ') if args and kwargs: - raise TypeError( - "jsonify() behavior undefined when passed both args and kwargs" - ) + raise TypeError('jsonify() behavior undefined when passed both args and kwargs') elif len(args) == 1: # single args are passed directly to dumps() data = args[0] else: data = args or kwargs - # Note that we add '\n' to end of response - # (see https://github.com/mitsuhiko/flask/pull/1262) - rv = current_app.response_class( + return current_app.response_class( (dumps(data, indent=indent, separators=separators), '\n'), - mimetype='application/json') - return rv + mimetype='application/json' + ) def tojson_filter(obj, **kwargs): From a4df0fbb397e13bd7c6475dbde5f44727474c3c7 Mon Sep 17 00:00:00 2001 From: Adrian Date: Tue, 2 Feb 2016 16:16:01 +0100 Subject: [PATCH 077/440] Add missing return to g.setdefault --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 1e01df78..3401bd79 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -36,7 +36,7 @@ class _AppCtxGlobals(object): return self.__dict__.pop(name, default) def setdefault(self, name, default=None): - self.__dict__.setdefault(name, default) + return self.__dict__.setdefault(name, default) def __contains__(self, item): return item in self.__dict__ From 6d0bbd627c8ff7b6339c894342d00eae11deb7ec Mon Sep 17 00:00:00 2001 From: lord63 Date: Wed, 3 Feb 2016 20:49:02 +0800 Subject: [PATCH 078/440] Fix typo * Use the compatible way to handle the exception. You can find the source code wsgi_app in app.py, and it use the compatible way, so update it * Fix typo in config.py * Fix typo in app.py --- docs/reqcontext.rst | 2 +- flask/app.py | 2 +- flask/config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index d7d74fbf..51cd66f6 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -69,7 +69,7 @@ find a piece of code that looks very much like this:: with self.request_context(environ): try: response = self.full_dispatch_request() - except Exception, e: + except Exception as e: response = self.make_response(self.handle_exception(e)) return response(environ, start_response) diff --git a/flask/app.py b/flask/app.py index f75dd818..02d470ed 100644 --- a/flask/app.py +++ b/flask/app.py @@ -421,7 +421,7 @@ class Flask(_PackageBoundObject): #: A dictionary with lists of functions that should be called after #: each request. The key of the dictionary is the name of the blueprint #: this function is active for, ``None`` for all requests. This can for - #: example be used to open database connections or getting hold of the + #: example be used to close database connections or getting hold of the #: currently logged in user. To register a function here, use the #: :meth:`after_request` decorator. self.after_request_funcs = {} diff --git a/flask/config.py b/flask/config.py index d2eeb837..6f643a99 100644 --- a/flask/config.py +++ b/flask/config.py @@ -222,7 +222,7 @@ class Config(dict): app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' image_store_config = app.config.get_namespace('IMAGE_STORE_') - The resulting dictionary `image_store` would look like:: + The resulting dictionary `image_store_config` would look like:: { 'type': 'fs', From 07fdd1930b947b2cc074706ecdfea908ffdb05b9 Mon Sep 17 00:00:00 2001 From: lord63 Date: Thu, 4 Feb 2016 14:33:02 +0800 Subject: [PATCH 079/440] Update app.py --- flask/app.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/flask/app.py b/flask/app.py index 02d470ed..da1998c6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -421,9 +421,8 @@ class Flask(_PackageBoundObject): #: A dictionary with lists of functions that should be called after #: each request. The key of the dictionary is the name of the blueprint #: this function is active for, ``None`` for all requests. This can for - #: example be used to close database connections or getting hold of the - #: currently logged in user. To register a function here, use the - #: :meth:`after_request` decorator. + #: example be used to close database connections. To register a function + #: here, use the :meth:`after_request` decorator. self.after_request_funcs = {} #: A dictionary with lists of functions that are called after @@ -791,8 +790,7 @@ class Flask(_PackageBoundObject): It is not recommended to use this function for development with automatic reloading as this is badly supported. Instead you should - be using the :command:`flask` command line script's ``runserver`` - support. + be using the :command:`flask` command line script's ``run`` support. .. admonition:: Keep in Mind From 6c0496a1f38e69c700a2ca834d3d64f60361d3bc Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Mon, 8 Feb 2016 19:33:34 +0530 Subject: [PATCH 080/440] Fix a typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `interchangable` → `interchangeable` --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index f51d7460..6f000717 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -56,7 +56,7 @@ Those two ways are equivalent, but the first one is more clear and leaves you with a function to call on your whim (and in tests). Note that :exc:`werkzeug.exceptions.HTTPException` subclasses like :exc:`~werkzeug.exceptions.BadRequest` from the example and their HTTP codes -are interchangable when handed to the registration methods or decorator +are interchangeable when handed to the registration methods or decorator (``BadRequest.code == 400``). You are however not limited to a :exc:`~werkzeug.exceptions.HTTPException` From 52daeffdc9ef07019acaf64ce505aa54ba98a037 Mon Sep 17 00:00:00 2001 From: Eric Dill Date: Tue, 9 Feb 2016 14:24:09 -0500 Subject: [PATCH 081/440] DOC: Remove gendered language --- docs/patterns/fileuploads.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 10aa15f2..7a3cd28e 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -40,7 +40,7 @@ your users to be able to upload everything there if the server is directly sending out the data to the client. That way you can make sure that users are not able to upload HTML files that would cause XSS problems (see :ref:`xss`). Also make sure to disallow ``.php`` files if the server -executes them, but who has PHP installed on his server, right? :) +executes them, but who has PHP installed on their server, right? :) Next the functions that check if an extension is valid and that uploads the file and redirects the user to the URL for the uploaded file:: From 39cb3504e155290958735b2ffafb69ff23b23c4f Mon Sep 17 00:00:00 2001 From: Brian Welch Date: Fri, 19 Feb 2016 08:23:28 -0500 Subject: [PATCH 082/440] Update MANIFEST.in with simpler template commands Please see https://docs.python.org/2/distutils/sourcedist.html#commands for reference. --- MANIFEST.in | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index ce115864..f8d9c2ae 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,13 +1,11 @@ include Makefile CHANGES LICENSE AUTHORS -recursive-include artwork * -recursive-include tests * -recursive-include examples * -recursive-include docs * -recursive-exclude docs *.pyc -recursive-exclude docs *.pyo -recursive-exclude tests *.pyc -recursive-exclude tests *.pyo -recursive-exclude examples *.pyc -recursive-exclude examples *.pyo + +graft artwork +graft tests +graft examples +graft docs + +global-exclude *.py[co] + prune docs/_build prune docs/_themes From 4dc2ef19eaa4ced5cb2f4a2a1673e65459c355d2 Mon Sep 17 00:00:00 2001 From: Reuven Date: Fri, 4 Mar 2016 13:30:40 +0200 Subject: [PATCH 083/440] Use pytest.raises() instead of try/catch with asser 0 This is somehow more readable, and enable using the features of pytest's ExeptionInfo (such as errisinstance). --- tests/test_basic.py | 50 ++++++++++++---------------------------- tests/test_blueprints.py | 7 ++---- tests/test_config.py | 46 ++++++++++++++---------------------- tests/test_reqctx.py | 6 +---- tests/test_testing.py | 14 ++++------- 5 files changed, 39 insertions(+), 84 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index d7cc7a3f..3daca9cd 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -307,12 +307,8 @@ def test_missing_session(): app = flask.Flask(__name__) def expect_exception(f, *args, **kwargs): - try: - f(*args, **kwargs) - except RuntimeError as e: - assert e.args and 'session is unavailable' in e.args[0] - else: - assert False, 'expected exception' + e = pytest.raises(RuntimeError, f, *args, **kwargs) + assert e.value.args and 'session is unavailable' in e.value.args[0] with app.test_request_context(): assert flask.session.get('missing_key') is None expect_exception(flask.session.__setitem__, 'foo', 42) @@ -853,12 +849,9 @@ def test_trapping_of_bad_request_key_errors(): app.config['TRAP_BAD_REQUEST_ERRORS'] = True c = app.test_client() - try: - c.get('/fail') - except KeyError as e: - assert isinstance(e, BadRequest) - else: - assert False, 'Expected exception' + with pytest.raises(KeyError) as e: + c.get("/fail") + assert e.errisinstance(BadRequest) def test_trapping_of_all_http_exceptions(): @@ -888,13 +881,10 @@ def test_enctype_debug_helper(): # stack otherwise and we want to ensure that this is not the case # to not negatively affect other tests. with app.test_client() as c: - try: + with pytest.raises(DebugFilesKeyError) as e: c.post('/fail', data={'foo': 'index.txt'}) - except DebugFilesKeyError as e: - assert 'no file contents were transmitted' in str(e) - assert 'This was submitted: "index.txt"' in str(e) - else: - assert False, 'Expected exception' + assert 'no file contents were transmitted' in str(e.value) + assert 'This was submitted: "index.txt"' in str(e.value) def test_response_creation(): @@ -1203,12 +1193,8 @@ def test_exception_propagation(): c = app.test_client() if config_key is not None: app.config[config_key] = True - try: + with pytest.raises(Exception): c.get('/') - except Exception: - pass - else: - assert False, 'expected exception' else: assert c.get('/').status_code == 500 @@ -1345,14 +1331,11 @@ def test_debug_mode_complains_after_first_request(): return 'Awesome' assert not app.got_first_request assert app.test_client().get('/').data == b'Awesome' - try: + with pytest.raises(AssertionError) as e: @app.route('/foo') def broken(): return 'Meh' - except AssertionError as e: - assert 'A setup function was called' in str(e) - else: - assert False, 'Expected exception' + assert 'A setup function was called' in str(e) app.debug = False @@ -1408,14 +1391,11 @@ def test_routing_redirect_debugging(): def foo(): return 'success' with app.test_client() as c: - try: + with pytest.raises(AssertionError) as e: c.post('/foo', data={}) - except AssertionError as e: - assert 'http://localhost/foo/' in str(e) - assert ('Make sure to directly send ' - 'your POST-request to this URL') in str(e) - else: - assert False, 'Expected exception' + assert 'http://localhost/foo/' in str(e) + assert ('Make sure to directly send ' + 'your POST-request to this URL') in str(e) rv = c.get('/foo', data={}, follow_redirects=True) assert rv.data == b'success' diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 7e22bc7f..a3309037 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -174,12 +174,9 @@ def test_templates_and_static(test_apps): assert flask.url_for('admin.static', filename='test.txt') == '/admin/static/test.txt' with app.test_request_context(): - try: + with pytest.raises(TemplateNotFound) as e: flask.render_template('missing.html') - except TemplateNotFound as e: - assert e.name == 'missing.html' - else: - assert 0, 'expected exception' + assert e.value.name == 'missing.html' with flask.Flask(__name__).test_request_context(): assert flask.render_template('nested/nested.txt') == 'I\'m nested' diff --git a/tests/test_config.py b/tests/test_config.py index 7a17b607..333a5cff 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -89,12 +89,9 @@ def test_config_from_envvar(): try: os.environ = {} app = flask.Flask(__name__) - try: + with pytest.raises(RuntimeError) as e: app.config.from_envvar('FOO_SETTINGS') - except RuntimeError as e: - assert "'FOO_SETTINGS' is not set" in str(e) - else: - assert 0, 'expected exception' + assert "'FOO_SETTINGS' is not set" in str(e.value) assert not app.config.from_envvar('FOO_SETTINGS', silent=True) os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'} @@ -108,16 +105,13 @@ def test_config_from_envvar_missing(): env = os.environ try: os.environ = {'FOO_SETTINGS': 'missing.cfg'} - try: + with pytest.raises(IOError) as e: app = flask.Flask(__name__) app.config.from_envvar('FOO_SETTINGS') - except IOError as e: - msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.cfg'") - else: - assert False, 'expected IOError' + msg = str(e.value) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") assert not app.config.from_envvar('FOO_SETTINGS', silent=True) finally: os.environ = env @@ -125,29 +119,23 @@ def test_config_from_envvar_missing(): def test_config_missing(): app = flask.Flask(__name__) - try: + with pytest.raises(IOError) as e: app.config.from_pyfile('missing.cfg') - except IOError as e: - msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.cfg'") - else: - assert 0, 'expected config' + msg = str(e.value) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") assert not app.config.from_pyfile('missing.cfg', silent=True) def test_config_missing_json(): app = flask.Flask(__name__) - try: + with pytest.raises(IOError) as e: app.config.from_json('missing.json') - except IOError as e: - msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.json'") - else: - assert 0, 'expected config' + msg = str(e.value) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.json'") assert not app.config.from_json('missing.json', silent=True) diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index 558958dc..4b2b1f87 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -140,12 +140,8 @@ def test_manual_context_binding(): ctx.push() assert index() == 'Hello World!' ctx.pop() - try: + with pytest.raises(RuntimeError): index() - except RuntimeError: - pass - else: - assert 0, 'expected runtime error' @pytest.mark.skipif(greenlet is None, reason='greenlet not installed') def test_greenlet_context_copying(): diff --git a/tests/test_testing.py b/tests/test_testing.py index a9e06fab..7bb99e79 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -100,13 +100,10 @@ def test_session_transactions_no_null_sessions(): app.testing = True with app.test_client() as c: - try: + with pytest.raises(RuntimeError) as e: with c.session_transaction() as sess: pass - except RuntimeError as e: - assert 'Session backend did not open a session' in str(e) - else: - assert False, 'Expected runtime error' + assert 'Session backend did not open a session' in str(e.value) def test_session_transactions_keep_context(): app = flask.Flask(__name__) @@ -124,13 +121,10 @@ def test_session_transaction_needs_cookies(): app = flask.Flask(__name__) app.testing = True c = app.test_client(use_cookies=False) - try: + with pytest.raises(RuntimeError) as e: with c.session_transaction() as s: pass - except RuntimeError as e: - assert 'cookies' in str(e) - else: - assert False, 'Expected runtime error' + assert 'cookies' in str(e.value) def test_test_client_context_binding(): app = flask.Flask(__name__) From 67e2127a5d9de5c17af122cb5fcacfc05e980a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Petit?= Date: Tue, 8 Mar 2016 10:05:20 +0100 Subject: [PATCH 084/440] Update deprecated references validators.Required() is marked as deprecated in favor of validators.DataRequired() and will be remove in WTForms 3. --- docs/patterns/wtforms.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 88602b6c..5a84fce8 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -32,11 +32,11 @@ This is an example form for a typical registration page:: username = StringField('Username', [validators.Length(min=4, max=25)]) email = StringField('Email Address', [validators.Length(min=6, max=35)]) password = PasswordField('New Password', [ - validators.Required(), + validators.DataRequired(), validators.EqualTo('confirm', message='Passwords must match') ]) confirm = PasswordField('Repeat Password') - accept_tos = BooleanField('I accept the TOS', [validators.Required()]) + accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()]) In the View ----------- From adeedddbb53083add5f9e66041b072ad716a2aab Mon Sep 17 00:00:00 2001 From: Ethan Rogers Date: Fri, 11 Mar 2016 10:06:00 -0700 Subject: [PATCH 085/440] Fix minor typo in security doc --- docs/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security.rst b/docs/security.rst index 3e97834d..52dccfab 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -73,7 +73,7 @@ them knowing. Say you have a specific URL that, when you sent ``POST`` requests to will delete a user's profile (say ``http://example.com/user/delete``). If an attacker now creates a page that sends a post request to that page with -some JavaScript they just has to trick some users to load that page and +some JavaScript they just have to trick some users to load that page and their profiles will end up being deleted. Imagine you were to run Facebook with millions of concurrent users and From bc9619bebac93d3a44777ef3ebfd7b12b3399cc3 Mon Sep 17 00:00:00 2001 From: whiteUnicorn Date: Tue, 15 Mar 2016 00:02:29 +0900 Subject: [PATCH 086/440] Replace ';' to ',' I think it is awkward. Though semicolon can be used as a kind of supercomma, this sentence is not that kine of thing. Replacing it with comma would be more better. isn't it? --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 4f1ceaaf..6491774e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -162,7 +162,7 @@ and :command:`python` which will run those things, but this might not automatica on Windows, because it doesn't know where those executables are (give either a try!). To fix this, you should be able to navigate to your Python install directory -(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`; then find the +(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the :file:`win_add2path.py` file and run that. Open a **new** Command Prompt and check that you can now just type :command:`python` to bring up the interpreter. From fc0e1a8d8adc10ee602a5621c3a1382eb36702f2 Mon Sep 17 00:00:00 2001 From: Austen D'Souza Date: Wed, 23 Mar 2016 02:02:04 +0530 Subject: [PATCH 087/440] Fixed typo I think it's supposed to be an upper-case 'F'. Cheers :) --- docs/tutorial/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index d30bd5cf..e3da4b97 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -3,7 +3,7 @@ Introducing Flaskr ================== -We will call our blogging application flaskr, but feel free to choose your own +We will call our blogging application Flaskr, but feel free to choose your own less Web-2.0-ish name ;) Essentially, we want it to do the following things: 1. Let the user sign in and out with credentials specified in the From 55e37d4f0937cc143706cdb9052039b7b8cf477c Mon Sep 17 00:00:00 2001 From: gunbei Date: Wed, 23 Mar 2016 11:27:05 +0100 Subject: [PATCH 088/440] update get-pip.py location --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 6491774e..f20c7a65 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -172,5 +172,5 @@ Finally, to install `virtualenv`_, you can simply run:: Then you can be off on your way following the installation instructions above. -.. _get-pip.py: https://raw.githubusercontent.com/pypa/pip/master/contrib/get-pip.py +.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py .. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py From ce06240851d18ab725d348bce1793aad67349131 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sun, 27 Mar 2016 10:36:57 -0700 Subject: [PATCH 089/440] Run bdist_wheel as a part of release process The setup.cfg declares the ability to compile a wheel, but release process isn't actually compiling a wheel. --- scripts/make-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make-release.py b/scripts/make-release.py index 4a74cb59..5c16b6fa 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -86,7 +86,7 @@ def set_setup_version(version): def build_and_upload(): - Popen([sys.executable, 'setup.py', 'release', 'sdist', 'upload']).wait() + Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel', 'upload']).wait() def fail(message, *args): From e0a8fd3162ebc1fb1c2edadf91325f0b586d7425 Mon Sep 17 00:00:00 2001 From: lord63 Date: Sat, 2 Apr 2016 05:05:11 +0800 Subject: [PATCH 090/440] Add two missing converters for flask in the docs All converters are from werkzeug's builtin converters. Documentation: http://werkzeug.pocoo.org/docs/dev/routing/#builtin-converters --- docs/api.rst | 2 ++ docs/quickstart.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 3da975e9..9d9d3b1a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -751,6 +751,8 @@ The following converters are available: `int` accepts integers `float` like `int` but for floating point values `path` like the default but also accepts slashes +`any` matches one of the items provided +`uuid` accepts UUID strings =========== =============================================== Custom converters can be defined using :attr:`flask.Flask.url_map`. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 38c14035..bc6f0789 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -204,6 +204,8 @@ The following converters exist: `int` accepts integers `float` like `int` but for floating point values `path` like the default but also accepts slashes +`any` matches one of the items provided +`uuid` accepts UUID strings =========== =============================================== .. admonition:: Unique URLs / Redirection Behavior From 9a80fe691dfd46664a39edf983332b99a71766d1 Mon Sep 17 00:00:00 2001 From: bagratte Date: Sat, 2 Apr 2016 01:12:25 +0400 Subject: [PATCH 091/440] minor revision of documentation. --- docs/appcontext.rst | 4 ++-- docs/config.rst | 2 +- docs/deploying/mod_wsgi.rst | 2 +- docs/deploying/uwsgi.rst | 6 ------ docs/errorhandling.rst | 11 ++++++----- docs/htmlfaq.rst | 2 +- docs/patterns/appdispatch.rst | 14 +++++++------- docs/patterns/deferredcallbacks.rst | 6 +++--- docs/quickstart.rst | 2 +- docs/signals.rst | 6 +++--- docs/testing.rst | 4 ++-- docs/tutorial/dbinit.rst | 4 ++-- docs/tutorial/views.rst | 6 +++--- 13 files changed, 32 insertions(+), 37 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index 07e44f94..672b6bfd 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -26,8 +26,8 @@ In contrast, during request handling, a couple of other rules exist: There is a third state which is sitting in between a little bit. Sometimes you are dealing with an application in a way that is similar to -how you interact with applications during request handling just that there -is no request active. Consider for instance that you're sitting in an +how you interact with applications during request handling; just that there +is no request active. Consider, for instance, that you're sitting in an interactive Python shell and interacting with the application, or a command line application. diff --git a/docs/config.rst b/docs/config.rst index 245d3bab..8cef0686 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -330,7 +330,7 @@ there are alternative ways as well. For example you could use imports or subclassing. What is very popular in the Django world is to make the import explicit in -the config file by adding an ``from yourapplication.default_settings +the config file by adding ``from yourapplication.default_settings import *`` to the top of the file and then overriding the changes by hand. You could also inspect an environment variable like ``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index 9145b4f9..b06a1904 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -143,7 +143,7 @@ Troubleshooting If your application does not run, follow this guide to troubleshoot: **Problem:** application does not run, errorlog shows SystemExit ignored - You have a ``app.run()`` call in your application file that is not + You have an ``app.run()`` call in your application file that is not guarded by an ``if __name__ == '__main__':`` condition. Either remove that :meth:`~flask.Flask.run` call from the file and move it into a separate :file:`run.py` file or put it into such an if block. diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 2d15440f..3bb2a45c 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -27,12 +27,6 @@ Starting your app with uwsgi Given a flask application in myapp.py, use the following command: -.. sourcecode:: text - - $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app - -Or, if you prefer: - .. sourcecode:: text $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 6f000717..1e6a771f 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -59,13 +59,14 @@ you with a function to call on your whim (and in tests). Note that are interchangeable when handed to the registration methods or decorator (``BadRequest.code == 400``). -You are however not limited to a :exc:`~werkzeug.exceptions.HTTPException` -or its code but can register a handler for every exception class you like. +You are however not limited to :exc:`~werkzeug.exceptions.HTTPException` +or HTTP status codes but can register a handler for every exception class you +like. .. versionchanged:: 1.0 - Errorhandlers are now prioritized by specifity instead of the order they're - registered in. + Errorhandlers are now prioritized by specificity of the exception classes + they are registered for instead of the order they are registered in. Handling ```````` @@ -74,7 +75,7 @@ Once an exception instance is raised, its class hierarchy is traversed, and searched for in the exception classes for which handlers are registered. The most specific handler is selected. -E.g. if a instance of :exc:`ConnectionRefusedError` is raised, and a handler +E.g. if an instance of :exc:`ConnectionRefusedError` is raised, and a handler is registered for :exc:`ConnectionError` and :exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError` handler is called on the exception instance, and its response is shown to the user. diff --git a/docs/htmlfaq.rst b/docs/htmlfaq.rst index cba99fa1..0b6ff504 100644 --- a/docs/htmlfaq.rst +++ b/docs/htmlfaq.rst @@ -30,7 +30,7 @@ the (X)HTML generation on the web is based on non-XML template engines (such as Jinja, the one used in Flask) which do not protect you from accidentally creating invalid XHTML. There are XML based template engines, such as Kid and the popular Genshi, but they often come with a larger -runtime overhead and, are not as straightforward to use because they have +runtime overhead and are not as straightforward to use because they have to obey XML rules. The majority of users, however, assumed they were properly using XHTML. diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 3ff99e09..726850e2 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -4,11 +4,11 @@ Application Dispatching ======================= Application dispatching is the process of combining multiple Flask -applications on the WSGI level. You can not only combine Flask -applications into something larger but any WSGI application. This would -even allow you to run a Django and a Flask application in the same -interpreter side by side if you want. The usefulness of this depends on -how the applications work internally. +applications on the WSGI level. You can combine not only Flask +applications but any WSGI application. This would allow you to run a +Django and a Flask application in the same interpreter side by side if +you want. The usefulness of this depends on how the applications work +internally. The fundamental difference from the :ref:`module approach ` is that in this case you are running the same or @@ -31,7 +31,7 @@ Note that :func:`run_simple ` is not intended for use in production. Use a :ref:`full-blown WSGI server `. In order to use the interactive debugger, debugging must be enabled both on -the application and the simple server, here is the "hello world" example with +the application and the simple server. Here is the "hello world" example with debugging and :func:`run_simple `:: from flask import Flask @@ -56,7 +56,7 @@ If you have entirely separated applications and you want them to work next to each other in the same Python interpreter process you can take advantage of the :class:`werkzeug.wsgi.DispatcherMiddleware`. The idea here is that each Flask application is a valid WSGI application and they -are combined by the dispatcher middleware into a larger one that +are combined by the dispatcher middleware into a larger one that is dispatched based on prefix. For example you could have your main application run on ``/`` and your diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst index 83d4fb49..886ae40a 100644 --- a/docs/patterns/deferredcallbacks.rst +++ b/docs/patterns/deferredcallbacks.rst @@ -56,9 +56,9 @@ this the following function needs to be registered as A Practical Example ------------------- -Now we can easily at any point in time register a function to be called at -the end of this particular request. For example you can remember the -current language of the user in a cookie in the before-request function:: +At any time during a request, we can register a function to be called at the +end of the request. For example you can remember the current language of the +user in a cookie in the before-request function:: from flask import request diff --git a/docs/quickstart.rst b/docs/quickstart.rst index bc6f0789..2866b6d7 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -321,7 +321,7 @@ You have no idea what an HTTP method is? Worry not, here is a quick introduction to HTTP methods and why they matter: The HTTP method (also often called "the verb") tells the server what the -clients wants to *do* with the requested page. The following methods are +client wants to *do* with the requested page. The following methods are very common: ``GET`` diff --git a/docs/signals.rst b/docs/signals.rst index b368194c..2426e920 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -19,9 +19,9 @@ more. Also keep in mind that signals are intended to notify subscribers and should not encourage subscribers to modify data. You will notice that there are signals that appear to do the same thing like some of the builtin decorators do (eg: :data:`~flask.request_started` is very similar -to :meth:`~flask.Flask.before_request`). There are however difference in -how they work. The core :meth:`~flask.Flask.before_request` handler for -example is executed in a specific order and is able to abort the request +to :meth:`~flask.Flask.before_request`). However, there are differences in +how they work. The core :meth:`~flask.Flask.before_request` handler, for +example, is executed in a specific order and is able to abort the request early by returning a response. In contrast all signal handlers are executed in undefined order and do not modify any data. diff --git a/docs/testing.rst b/docs/testing.rst index 6387202b..493ba320 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -223,8 +223,8 @@ there does not seem to be a good way to do that, consider switching to application factories (see :ref:`app-factories`). Note however that if you are using a test request context, the -:meth:`~flask.Flask.before_request` functions are not automatically called -same for :meth:`~flask.Flask.after_request` functions. However +:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request` +functions are not called automatically. However :meth:`~flask.Flask.teardown_request` functions are indeed executed when the test request context leaves the ``with`` block. If you do want the :meth:`~flask.Flask.before_request` functions to be called as well, you diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index b3cf39dc..ebe9ce44 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -38,12 +38,12 @@ add this function below the `connect_db` function in :file:`flaskr.py`:: The ``app.cli.command()`` decorator registers a new command with the :command:`flask` script. When the command executes, Flask will automatically -create a application context for us bound to the right application. +create an application context for us bound to the right application. Within the function, we can then access :attr:`flask.g` and other things as we would expect. When the script ends, the application context tears down and the database connection is released. -We want to keep an actual functions around that initializes the database, +We want to keep an actual function around that initializes the database, though, so that we can easily create databases in unit tests later on. (For more information see :ref:`testing`.) diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 10b578a7..8ecc41a2 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -12,11 +12,11 @@ Show Entries This view shows all the entries stored in the database. It listens on the root of the application and will select title and text from the database. The one with the highest id (the newest entry) will be on top. The rows -returned from the cursor look a bit like tuples because we are using +returned from the cursor look a bit like dictionaries because we are using the :class:`sqlite3.Row` row factory. -The view function will pass the entries as dictionaries to the -:file:`show_entries.html` template and return the rendered one:: +The view function will pass the entries to the :file:`show_entries.html` +template and return the rendered one:: @app.route('/') def show_entries(): From cc536c8a7b6f61fedb2d0cb1abdd643d8e987afe Mon Sep 17 00:00:00 2001 From: Shipeng Feng Date: Sat, 2 Apr 2016 07:17:45 +0800 Subject: [PATCH 092/440] Fixed stream_with_context if decorated function has parameters --- flask/helpers.py | 2 +- tests/test_helpers.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 3210772d..a7d8f97c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -101,7 +101,7 @@ def stream_with_context(generator_or_function): gen = iter(generator_or_function) except TypeError: def decorator(*args, **kwargs): - gen = generator_or_function() + gen = generator_or_function(*args, **kwargs) return stream_with_context(gen) return update_wrapper(decorator, generator_or_function) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 620fd792..2fe2ead5 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -683,11 +683,11 @@ class TestStreaming(object): @app.route('/') def index(): @flask.stream_with_context - def generate(): - yield 'Hello ' + def generate(hello): + yield hello yield flask.request.args['name'] yield '!' - return flask.Response(generate()) + return flask.Response(generate('Hello ')) c = app.test_client() rv = c.get('/?name=World') assert rv.data == b'Hello World!' From d3d8a4694a9c1601e213d6b5f27fab34615609b7 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 2 Apr 2016 21:06:30 +0200 Subject: [PATCH 093/440] Deprecate flask.ext * Add deprecation warning to ext pkg * Add docs on deprecation of flask.ext * Improve deprecation warnings * Add headers for better distinction, fix ordering issue of paragraphs --- docs/extensiondev.rst | 26 ++++++++++++-------------- docs/extensions.rst | 7 ++++--- docs/upgrading.rst | 29 +++++++++++++++++++++++++---- flask/exthook.py | 25 ++++++++++++++++++++++++- 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 918187fe..9a4cd14c 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -360,8 +360,7 @@ extension to be approved you have to follow these guidelines: find a new maintainer including full source hosting transition and PyPI access. If no maintainer is available, give access to the Flask core team. 1. An approved Flask extension must provide exactly one package or module - named ``flask_extensionname``. They might also reside inside a - ``flaskext`` namespace packages though this is discouraged now. + named ``flask_extensionname``. 2. It must ship a testing suite that can either be invoked with ``make test`` or ``python setup.py test``. For test suites invoked with ``make test`` the extension has to ensure that all dependencies for the test @@ -399,20 +398,19 @@ extension to be approved you have to follow these guidelines: Extension Import Transition --------------------------- -For a while we recommended using namespace packages for Flask extensions. -This turned out to be problematic in practice because many different -competing namespace package systems exist and pip would automatically -switch between different systems and this caused a lot of problems for -users. +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. -Instead we now recommend naming packages ``flask_foo`` instead of the now -deprecated ``flaskext.foo``. Flask 0.8 introduces a redirect import -system that lets uses import from ``flask.ext.foo`` and it will try -``flask_foo`` first and if that fails ``flaskext.foo``. +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. -Flask extensions should urge users to import from ``flask.ext.foo`` -instead of ``flask_foo`` or ``flaskext_foo`` so that extensions can -transition to the new package name without affecting users. +As of Flask 1.0, most Flask extensions have transitioned to the new naming +schema. The ``flask.ext.foo`` compatibility alias is still in Flask 1.0 but is +now deprecated -- you should use ``flask_foo``. .. _OAuth extension: http://pythonhosted.org/Flask-OAuth/ diff --git a/docs/extensions.rst b/docs/extensions.rst index 748b11b3..1371f823 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -18,10 +18,10 @@ 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 will be always -importable from ``flask.ext.foo``:: +extension called ``Flask-Foo`` or ``Foo-Flask`` it should be always +importable from ``flask_foo``:: - from flask.ext import foo + import flask_foo Flask Before 0.8 ---------------- @@ -44,5 +44,6 @@ And here is how you can use it:: Once the ``flaskext_compat`` module is activated the :data:`flask.ext` will exist and you can start importing from there. + .. _Flask Extension Registry: http://flask.pocoo.org/extensions/ .. _flaskext_compat.py: https://raw.githubusercontent.com/mitsuhiko/flask/master/scripts/flaskext_compat.py diff --git a/docs/upgrading.rst b/docs/upgrading.rst index fca4d75b..40f8b0bc 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -24,11 +24,17 @@ installation, make sure to pass it the :option:`-U` parameter:: Version 1.0 ----------- +Debugging ++++++++++ + Flask 1.0 removed the ``debug_log_format`` attribute from Flask applications. Instead the new ``LOGGER_HANDLER_POLICY`` configuration can be used to disable the default log handlers and custom log handlers can be set up. +Error handling +++++++++++++++ + The behavior of error handlers was changed. The precedence of handlers used to be based on the decoration/call order of :meth:`~flask.Flask.errorhandler` and @@ -37,9 +43,7 @@ Now the inheritance hierarchy takes precedence and handlers for more specific exception classes are executed instead of more general ones. See :ref:`error-handlers` for specifics. -The :func:`~flask.templating.render_template_string` function has changed to -autoescape template variables by default. This better matches the behavior -of :func:`~flask.templating.render_template`. +Trying to register a handler on an instance now raises :exc:`ValueError`. .. note:: @@ -47,8 +51,25 @@ of :func:`~flask.templating.render_template`. only for exception *instances*. This was unintended and plain wrong, and therefore was replaced with the intended behavior of registering handlers only using exception classes and HTTP error codes. + +Templating +++++++++++ + +The :func:`~flask.templating.render_template_string` function has changed to +autoescape template variables by default. This better matches the behavior +of :func:`~flask.templating.render_template`. - Trying to register a handler on an instance now raises :exc:`ValueError`. +Extension imports ++++++++++++++++++ + +Extension imports of the form ``flask.ext.foo`` are deprecated, you should use +``flask_foo``. + +The old form still works, but Flask will issue a +``flask.exthook.ExtDeprecationWarning`` for each extension you import the old +way. We also provide a migration utility called `flask-ext-migrate +`_ that is supposed to +automatically rewrite your imports for this. .. _upgrading-to-010: diff --git a/flask/exthook.py b/flask/exthook.py index 05ac4e35..6522e063 100644 --- a/flask/exthook.py +++ b/flask/exthook.py @@ -21,9 +21,16 @@ """ import sys import os +import warnings from ._compat import reraise +class ExtDeprecationWarning(DeprecationWarning): + pass + +warnings.simplefilter('always', ExtDeprecationWarning) + + class ExtensionImporter(object): """This importer redirects imports from this submodule to other locations. This makes it possible to transition from the old flaskext.name to the @@ -49,13 +56,21 @@ class ExtensionImporter(object): sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] def find_module(self, fullname, path=None): - if fullname.startswith(self.prefix): + if fullname.startswith(self.prefix) and \ + fullname != 'flask.ext.ExtDeprecationWarning': return self def load_module(self, fullname): if fullname in sys.modules: return sys.modules[fullname] + modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] + + warnings.warn( + "Importing flask.ext.{x} is deprecated, use flask_{x} instead." + .format(x=modname), ExtDeprecationWarning + ) + for path in self.module_choices: realname = path % modname try: @@ -83,6 +98,14 @@ class ExtensionImporter(object): module = sys.modules[fullname] = sys.modules[realname] if '.' not in modname: setattr(sys.modules[self.wrapper_module], modname, module) + + if realname.startswith('flaskext.'): + warnings.warn( + "Detected extension named flaskext.{x}, please rename it " + "to flask_{x}. The old form is deprecated." + .format(x=modname), ExtDeprecationWarning + ) + return module raise ImportError('No module named %s' % fullname) From 9f1be8e795ace494018689c87d8a5e5601313e4d Mon Sep 17 00:00:00 2001 From: David Hou Date: Sat, 2 Apr 2016 12:07:27 -0700 Subject: [PATCH 094/440] Raise BadRequest if static file name is invalid * Raise BadRequest if static file name is invalid * Clean up syntax a bit * Remove unnecessary close() --- flask/helpers.py | 9 ++++++--- tests/test_helpers.py | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index a7d8f97c..02e99e37 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -27,7 +27,7 @@ except ImportError: from urlparse import quote as url_quote from werkzeug.datastructures import Headers -from werkzeug.exceptions import NotFound +from werkzeug.exceptions import BadRequest, NotFound # this was moved in 0.7 try: @@ -618,8 +618,11 @@ def send_from_directory(directory, filename, **options): filename = safe_join(directory, filename) if not os.path.isabs(filename): filename = os.path.join(current_app.root_path, filename) - if not os.path.isfile(filename): - raise NotFound() + try: + if not os.path.isfile(filename): + raise NotFound() + except (TypeError, ValueError): + raise BadRequest() options.setdefault('conditional', True) return send_file(filename, **options) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 2fe2ead5..5605c45d 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -15,6 +15,7 @@ import os import datetime import flask from logging import StreamHandler +from werkzeug.exceptions import BadRequest from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date from flask._compat import StringIO, text_type @@ -504,6 +505,14 @@ class TestSendfile(object): assert rv.data.strip() == b'Hello Subdomain' rv.close() + def test_send_from_directory_bad_request(self): + app = flask.Flask(__name__) + app.testing = True + app.root_path = os.path.join(os.path.dirname(__file__), + 'test_apps', 'subdomaintestmodule') + with app.test_request_context(): + with pytest.raises(BadRequest): + flask.send_from_directory('static', 'bad\x00') class TestLogging(object): From 7f26d45b16d3fa3593360ea9dc0243cc0dbcb6bd Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 2 Apr 2016 21:08:58 +0200 Subject: [PATCH 095/440] Changelog for #1484 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0337ec47..157b2a59 100644 --- a/CHANGES +++ b/CHANGES @@ -73,6 +73,7 @@ Version 1.0 - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). +- ``flask.ext`` is now deprecated (pull request ``#1484``). Version 0.10.2 -------------- From bd667109c650c83cc3d5c50de509567ca8472fd3 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 2 Apr 2016 21:10:24 +0200 Subject: [PATCH 096/440] Changelog for #1763 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 157b2a59..7e2877d0 100644 --- a/CHANGES +++ b/CHANGES @@ -74,6 +74,8 @@ Version 1.0 - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). - ``flask.ext`` is now deprecated (pull request ``#1484``). +- ``send_from_directory`` now raises BadRequest if the filename is invalid on + the server OS (pull request ``#1763``). Version 0.10.2 -------------- From 567fff9d0d20650743abd8fe17d3dc9d98fa19dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Garc=C3=ADa?= Date: Sun, 3 Apr 2016 23:11:38 +0200 Subject: [PATCH 097/440] Change to Pallets project * Update CHANGES * Update CONTRIBUTING.rst * Update setup.py * Update tox.ini * Update extensions.rst * Update security.rst * Update installation.rst * Update testing.rst * Update upgrading.rst * Update sidebarintro.html * Update jquery.rst * Update dbcon.rst * Update index.rst --- CHANGES | 2 +- CONTRIBUTING.rst | 2 +- docs/_templates/sidebarintro.html | 4 ++-- docs/extensions.rst | 2 +- docs/installation.rst | 2 +- docs/patterns/jquery.rst | 2 +- docs/security.rst | 2 +- docs/testing.rst | 4 ++-- docs/tutorial/dbcon.rst | 2 +- docs/tutorial/index.rst | 2 +- docs/upgrading.rst | 2 +- setup.py | 4 ++-- tox.ini | 6 +++--- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 7e2877d0..37fe8694 100644 --- a/CHANGES +++ b/CHANGES @@ -60,7 +60,7 @@ Version 1.0 - JSON responses are now terminated with a newline character, because it is a convention that UNIX text files end with a newline and some clients don't deal well when this newline is missing. See - https://github.com/mitsuhiko/flask/pull/1262 -- this came up originally as a + https://github.com/pallets/flask/pull/1262 -- this came up originally as a part of https://github.com/kennethreitz/httpbin/issues/168 - The automatically provided ``OPTIONS`` method is now correctly disabled if the user registered an overriding rule with the lowercase-version diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 99b75f69..be3f9363 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -45,7 +45,7 @@ install it with:: Clone this repository:: - git clone https://github.com/mitsuhiko/flask.git + git clone https://github.com/pallets/flask.git Install Flask as an editable package using the current source:: diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 25245591..ec1608fd 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -17,6 +17,6 @@

diff --git a/docs/extensions.rst b/docs/extensions.rst index 1371f823..c9af3035 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -46,4 +46,4 @@ exist and you can start importing from there. .. _Flask Extension Registry: http://flask.pocoo.org/extensions/ -.. _flaskext_compat.py: https://raw.githubusercontent.com/mitsuhiko/flask/master/scripts/flaskext_compat.py +.. _flaskext_compat.py: https://raw.githubusercontent.com/pallets/flask/master/scripts/flaskext_compat.py diff --git a/docs/installation.rst b/docs/installation.rst index f20c7a65..533a6fff 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -116,7 +116,7 @@ it to operate on a git checkout. Either way, virtualenv is recommended. Get the git checkout in a new virtualenv and run in development mode:: - $ git clone http://github.com/mitsuhiko/flask.git + $ git clone http://github.com/pallets/flask.git Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask $ virtualenv venv diff --git a/docs/patterns/jquery.rst b/docs/patterns/jquery.rst index c2e8838d..d136d5b4 100644 --- a/docs/patterns/jquery.rst +++ b/docs/patterns/jquery.rst @@ -164,5 +164,5 @@ explanation of the little bit of code above: If you don't get the whole picture, download the `sourcecode for this example -`_ +`_ from GitHub. diff --git a/docs/security.rst b/docs/security.rst index 52dccfab..587bd4ef 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -101,6 +101,6 @@ arrays to JSON. This was because of a security vulnerability in ECMAScript 4. ECMAScript 5 closed this vulnerability, so only extremely old browsers are still vulnerable. All of these browsers have `other more serious vulnerabilities -`_, so +`_, so this behavior was changed and :func:`~flask.jsonify` now supports serializing arrays. diff --git a/docs/testing.rst b/docs/testing.rst index 493ba320..fdf57937 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -24,7 +24,7 @@ the :ref:`tutorial`. If you don't have that application yet, get the sources from `the examples`_. .. _the examples: - https://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + https://github.com/pallets/flask/tree/master/examples/flaskr/ The Testing Skeleton -------------------- @@ -194,7 +194,7 @@ suite. .. _MiniTwit Example: - https://github.com/mitsuhiko/flask/tree/master/examples/minitwit/ + https://github.com/pallets/flask/tree/master/examples/minitwit/ Other Testing Tricks diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 8757114c..9a09ff3a 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -75,4 +75,4 @@ Continue to :ref:`tutorial-dbinit`. larger `, it's a good idea not to. .. _example source: - https://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + https://github.com/pallets/flask/tree/master/examples/flaskr/ diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 4b680b9b..80b9fc28 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -15,7 +15,7 @@ If you want the full source code in advance or for comparison, check out the `example source`_. .. _example source: - https://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ + https://github.com/pallets/flask/tree/master/examples/flaskr/ .. toctree:: :maxdepth: 2 diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 40f8b0bc..f6d29279 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -167,7 +167,7 @@ good. To apply the upgrade script do the following: 1. Download the script: `flask-07-upgrade.py - `_ + `_ 2. Run it in the directory of your application:: python flask-07-upgrade.py > patchfile.diff diff --git a/setup.py b/setup.py index 490ac02e..6cbc4306 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ Links * `website `_ * `documentation `_ * `development version - `_ + `_ """ import re @@ -57,7 +57,7 @@ with open('flask/__init__.py', 'rb') as f: setup( name='Flask', version=version, - url='http://github.com/mitsuhiko/flask/', + url='http://github.com/pallets/flask/', license='BSD', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', diff --git a/tox.ini b/tox.ini index a138877d..bd936a4b 100644 --- a/tox.ini +++ b/tox.ini @@ -15,9 +15,9 @@ deps= lowest: itsdangerous==0.21 lowest: blinker==1.0 release: blinker - devel: git+https://github.com/mitsuhiko/werkzeug.git - devel: git+https://github.com/mitsuhiko/jinja2.git - devel: git+https://github.com/mitsuhiko/itsdangerous.git + devel: git+https://github.com/pallets/werkzeug.git + devel: git+https://github.com/pallets/jinja.git + devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git [testenv:docs] From 6e91498e64f3081c9d37f8f6e5fd3e3086de9741 Mon Sep 17 00:00:00 2001 From: Steffen Prince Date: Sun, 4 Oct 2015 18:51:27 -0700 Subject: [PATCH 098/440] docs: run() should not be used in production Refs #1102 --- flask/app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 2d24d8b2..694f079f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -770,8 +770,13 @@ class Flask(_PackageBoundObject): return rv def run(self, host=None, port=None, debug=None, **options): - """Runs the application on a local development server. If the - :attr:`debug` flag is set the server will automatically reload + """Runs the application on a local development server. + + Do not use ``run()`` in a production setting. It is not intended to + meet security and performance requirements for a production server. + Instead, see :ref:`deployment` for WSGI server recommendations. + + If the :attr:`debug` flag is set the server will automatically reload for code changes and show a debugger in case an exception happened. If you want to run the application in debug mode, but disable the From b0105f41cc078b0f505787eebdb382014c685687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Raimbault?= Date: Sat, 28 Nov 2015 12:38:56 +0100 Subject: [PATCH 099/440] Extend documentation about uwsgi/nginx deployment --- docs/deploying/uwsgi.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 3bb2a45c..183bdb69 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -31,12 +31,14 @@ Given a flask application in myapp.py, use the following command: $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app -The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to -uwsgi, since its smarter about that. It is used together with the ``--mount`` -directive which will make requests to ``/yourapplication`` be directed to -``myapp:app``, where ``myapp`` refers to the name of the file of your flask -application (without extension). ``app`` is the callable inside of your -application (usually the line reads ``app = Flask(__name__)``. +The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi, +since its smarter about that. It is used together with the ``--mount`` directive +which will make requests to ``/yourapplication`` be directed to ``myapp:app``. +If your application is accessible at root level, you can use a single ``/`` +instead of ``/yourapplication``. ``myapp`` refers to the name of the file of +your flask application (without extension) or the module which provides ``app``. +``app`` is the callable inside of your application (usually the line reads +``app = Flask(__name__)``. If you want to deploy your flask application inside of a virtual environment, you need to also add ``--virtualenv /path/to/virtual/environment``. You might From 1a877fbaa027456cb7eba04f35d7f0a11c075f71 Mon Sep 17 00:00:00 2001 From: Keyan Pishdadian Date: Mon, 4 Apr 2016 17:19:10 -0400 Subject: [PATCH 100/440] Remove extmigrate from Flask repo * Remove extmigrate from Flask repo * Update docs to reflect new repo location --- docs/upgrading.rst | 2 +- scripts/flaskext_migrate.py | 162 ------------------------------- scripts/test_import_migration.py | 76 --------------- 3 files changed, 1 insertion(+), 239 deletions(-) delete mode 100644 scripts/flaskext_migrate.py delete mode 100644 scripts/test_import_migration.py diff --git a/docs/upgrading.rst b/docs/upgrading.rst index f6d29279..e89e8765 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -68,7 +68,7 @@ Extension imports of the form ``flask.ext.foo`` are deprecated, you should use The old form still works, but Flask will issue a ``flask.exthook.ExtDeprecationWarning`` for each extension you import the old way. We also provide a migration utility called `flask-ext-migrate -`_ that is supposed to +`_ that is supposed to automatically rewrite your imports for this. .. _upgrading-to-010: diff --git a/scripts/flaskext_migrate.py b/scripts/flaskext_migrate.py deleted file mode 100644 index 4d937007..00000000 --- a/scripts/flaskext_migrate.py +++ /dev/null @@ -1,162 +0,0 @@ -# Script which modifies source code away from the deprecated "flask.ext" -# format. -# -# Run in the terminal by typing: `python flaskext_migrate.py ` -# -# Author: Keyan Pishdadian 2015 - -from redbaron import RedBaron -import sys - - -def read_source(input_file): - """Parses the input_file into a RedBaron FST.""" - with open(input_file, "r") as source_code: - red = RedBaron(source_code.read()) - return red - - -def write_source(red, input_file): - """Overwrites the input_file once the FST has been modified.""" - with open(input_file, "w") as source_code: - source_code.write(red.dumps()) - - -def fix_imports(red): - """Wrapper which fixes "from" style imports and then "import" style.""" - red = fix_standard_imports(red) - red = fix_from_imports(red) - return red - - -def fix_from_imports(red): - """ - Converts "from" style imports to not use "flask.ext". - - Handles: - Case 1: from flask.ext.foo import bam --> from flask_foo import bam - Case 2: from flask.ext import foo --> import flask_foo as foo - """ - from_imports = red.find_all("FromImport") - for x, node in enumerate(from_imports): - values = node.value - if len(values) < 2: - continue - if (values[0].value == 'flask') and (values[1].value == 'ext'): - # Case 1 - if len(node.value) == 3: - package = values[2].value - modules = node.modules() - module_string = _get_modules(modules) - if len(modules) > 1: - node.replace("from flask_%s import %s" - % (package, module_string)) - else: - name = node.names()[0] - node.replace("from flask_%s import %s as %s" - % (package, module_string, name)) - # Case 2 - else: - module = node.modules()[0] - node.replace("import flask_%s as %s" - % (module, module)) - return red - - -def fix_standard_imports(red): - """ - Handles import modification in the form: - import flask.ext.foo" --> import flask_foo - """ - imports = red.find_all("ImportNode") - for x, node in enumerate(imports): - try: - if (node.value[0].value[0].value == 'flask' and - node.value[0].value[1].value == 'ext'): - package = node.value[0].value[2].value - name = node.names()[0].split('.')[-1] - if name == package: - node.replace("import flask_%s" % (package)) - else: - node.replace("import flask_%s as %s" % (package, name)) - except IndexError: - pass - - return red - - -def _get_modules(module): - """ - Takes a list of modules and converts into a string. - - The module list can include parens, this function checks each element in - the list, if there is a paren then it does not add a comma before the next - element. Otherwise a comma and space is added. This is to preserve module - imports which are multi-line and/or occur within parens. While also not - affecting imports which are not enclosed. - """ - modules_string = [cur + ', ' if cur.isalnum() and next.isalnum() - else cur - for (cur, next) in zip(module, module[1:]+[''])] - - return ''.join(modules_string) - - -def fix_function_calls(red): - """ - Modifies function calls in the source to reflect import changes. - - Searches the AST for AtomtrailerNodes and replaces them. - """ - atoms = red.find_all("Atomtrailers") - for x, node in enumerate(atoms): - try: - if (node.value[0].value == 'flask' and - node.value[1].value == 'ext'): - params = _form_function_call(node) - node.replace("flask_%s%s" % (node.value[2], params)) - except IndexError: - pass - - return red - - -def _form_function_call(node): - """ - Reconstructs function call strings when making attribute access calls. - """ - node_vals = node.value - output = "." - for x, param in enumerate(node_vals[3::]): - if param.dumps()[0] == "(": - output = output[0:-1] + param.dumps() - return output - else: - output += param.dumps() + "." - - -def check_user_input(): - """Exits and gives error message if no argument is passed in the shell.""" - if len(sys.argv) < 2: - sys.exit("No filename was included, please try again.") - - -def fix_tester(ast): - """Wrapper which allows for testing when not running from shell.""" - ast = fix_imports(ast) - ast = fix_function_calls(ast) - return ast.dumps() - - -def fix(): - """Wrapper for user argument checking and import fixing.""" - check_user_input() - input_file = sys.argv[1] - ast = read_source(input_file) - ast = fix_imports(ast) - ast = fix_function_calls(ast) - write_source(ast, input_file) - - -if __name__ == "__main__": - fix() diff --git a/scripts/test_import_migration.py b/scripts/test_import_migration.py deleted file mode 100644 index f000f2b1..00000000 --- a/scripts/test_import_migration.py +++ /dev/null @@ -1,76 +0,0 @@ -# Tester for the flaskext_migrate.py module located in flask/scripts/ -# -# Author: Keyan Pishdadian -import pytest -redbaron = pytest.importorskip("redbaron") -import flaskext_migrate as migrate - -def test_simple_from_import(): - red = redbaron.RedBaron("from flask.ext import foo") - output = migrate.fix_tester(red) - assert output == "import flask_foo as foo" - - -def test_from_to_from_import(): - red = redbaron.RedBaron("from flask.ext.foo import bar") - output = migrate.fix_tester(red) - assert output == "from flask_foo import bar as bar" - - -def test_multiple_import(): - red = redbaron.RedBaron("from flask.ext.foo import bar, foobar, something") - output = migrate.fix_tester(red) - assert output == "from flask_foo import bar, foobar, something" - - -def test_multiline_import(): - red = redbaron.RedBaron("from flask.ext.foo import \ - bar,\ - foobar,\ - something") - output = migrate.fix_tester(red) - assert output == "from flask_foo import bar, foobar, something" - - -def test_module_import(): - red = redbaron.RedBaron("import flask.ext.foo") - output = migrate.fix_tester(red) - assert output == "import flask_foo" - - -def test_named_module_import(): - red = redbaron.RedBaron("import flask.ext.foo as foobar") - output = migrate.fix_tester(red) - assert output == "import flask_foo as foobar" - - -def test_named_from_import(): - red = redbaron.RedBaron("from flask.ext.foo import bar as baz") - output = migrate.fix_tester(red) - assert output == "from flask_foo import bar as baz" - - -def test_parens_import(): - red = redbaron.RedBaron("from flask.ext.foo import (bar, foo, foobar)") - output = migrate.fix_tester(red) - assert output == "from flask_foo import (bar, foo, foobar)" - - -def test_function_call_migration(): - red = redbaron.RedBaron("flask.ext.foo(var)") - output = migrate.fix_tester(red) - assert output == "flask_foo(var)" - - -def test_nested_function_call_migration(): - red = redbaron.RedBaron("import flask.ext.foo\n\n" - "flask.ext.foo.bar(var)") - output = migrate.fix_tester(red) - assert output == ("import flask_foo\n\n" - "flask_foo.bar(var)") - - -def test_no_change_to_import(): - red = redbaron.RedBaron("from flask import Flask") - output = migrate.fix_tester(red) - assert output == "from flask import Flask" From 594d1c5eb274fd02cb31059becde848d9d38ed33 Mon Sep 17 00:00:00 2001 From: Winston Kouch Date: Tue, 5 Apr 2016 10:53:08 -0600 Subject: [PATCH 101/440] Add note to not use plain text passwords to views.rst --- docs/tutorial/views.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 8ecc41a2..618c97c6 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -94,5 +94,11 @@ if the user was logged in. session.pop('logged_in', None) flash('You were logged out') return redirect(url_for('show_entries')) + +Note that it is not a good idea to store passwords in plain text. You want to +protect login credentials if someone happens to have access to your database. +One way to do this is to use Security Helpers from Werkzeug to hash the +password. However, the emphasis of this tutorial is to demonstrate the basics +of Flask and plain text passwords are used for simplicity. Continue with :ref:`tutorial-templates`. From 8c595f1ebaaaa0de7d0f5ffb51c47e3f003a6a65 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Wed, 6 Apr 2016 23:30:41 -0700 Subject: [PATCH 102/440] Remove dotCloud host instructions dotCloud is no more; their parent company went bankrupt --- docs/deploying/index.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index ba506d6f..5d88cf72 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -19,8 +19,6 @@ Hosted options - `Deploying Flask on Heroku `_ - `Deploying Flask on OpenShift `_ -- `Deploying WSGI on dotCloud `_ - with `Flask-specific notes `_ - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ - `Sharing your Localhost Server with Localtunnel `_ From 740c42217cb349ab4f03ecba37973ec0c9bed421 Mon Sep 17 00:00:00 2001 From: Daniel Quinn Date: Fri, 8 Apr 2016 16:53:25 +0100 Subject: [PATCH 103/440] The comment didn't make any sense (#1777) Fix doc comment for View.methods --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 9bd6b615..6e249180 100644 --- a/flask/views.py +++ b/flask/views.py @@ -48,7 +48,7 @@ class View(object): generated view function! """ - #: A for which methods this pluggable view can handle. + #: A list of methods this view can handle. methods = None #: The canonical way to decorate class-based views is to decorate the From 0690ce18c276e5c0fd364663d14df6898d5e5ff4 Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 8 Apr 2016 20:47:08 +0200 Subject: [PATCH 104/440] Correct spelling for Stack Overflow --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index be3f9363..d72428e4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -10,7 +10,7 @@ Support questions Please, don't use the issue tracker for this. Check whether the ``#pocoo`` IRC channel on Freenode can help with your issue. If your problem is not strictly Werkzeug or Flask specific, ``#python`` is generally more active. -`StackOverflow `_ is also worth considering. +`Stack Overflow `_ is also worth considering. Reporting issues ================ From 2bf477cfeafd87c69b88e1ec58b5eb67abf756e2 Mon Sep 17 00:00:00 2001 From: Steven Loria Date: Fri, 8 Apr 2016 19:30:47 -0300 Subject: [PATCH 105/440] Add JSONIFY_MIMETYPE configuration variable (#1728) Allow jsonify responses' mimetype to be configured --- CHANGES | 1 + docs/config.rst | 1 + flask/app.py | 1 + flask/json.py | 2 +- tests/test_basic.py | 12 ++++++++++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 37fe8694..2befdc87 100644 --- a/CHANGES +++ b/CHANGES @@ -76,6 +76,7 @@ Version 1.0 - ``flask.ext`` is now deprecated (pull request ``#1484``). - ``send_from_directory`` now raises BadRequest if the filename is invalid on the server OS (pull request ``#1763``). +- Added the ``JSONIFY_MIMETYPE`` configuration variable (pull request ``#1728``). Version 0.10.2 -------------- diff --git a/docs/config.rst b/docs/config.rst index 8cef0686..1d9445d3 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -183,6 +183,7 @@ The following configuration values are used internally by Flask: if they are not requested by an XMLHttpRequest object (controlled by the ``X-Requested-With`` header) +``JSONIFY_MIMETYPE`` MIME type used for jsonify responses. ``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of the template source and reload it automatically. By default the value is diff --git a/flask/app.py b/flask/app.py index 7522c8e0..6a539106 100644 --- a/flask/app.py +++ b/flask/app.py @@ -315,6 +315,7 @@ class Flask(_PackageBoundObject): 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, + 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }) diff --git a/flask/json.py b/flask/json.py index cfc28c53..2bd47902 100644 --- a/flask/json.py +++ b/flask/json.py @@ -264,7 +264,7 @@ def jsonify(*args, **kwargs): return current_app.response_class( (dumps(data, indent=indent, separators=separators), '\n'), - mimetype='application/json' + mimetype=current_app.config['JSONIFY_MIMETYPE'] ) diff --git a/tests/test_basic.py b/tests/test_basic.py index 3daca9cd..8c5b0def 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1019,6 +1019,18 @@ def test_jsonify_prettyprint(): assert rv.data == pretty_response +def test_jsonify_mimetype(): + app = flask.Flask(__name__) + app.config.update({"JSONIFY_MIMETYPE": 'application/vnd.api+json'}) + with app.test_request_context(): + msg = { + "msg": {"submsg": "W00t"}, + } + rv = flask.make_response( + flask.jsonify(msg), 200) + assert rv.mimetype == 'application/vnd.api+json' + + def test_url_generation(): app = flask.Flask(__name__) From be2abd29267fdd337212b2d1a5856da635966005 Mon Sep 17 00:00:00 2001 From: Daniel Quinn Date: Mon, 11 Apr 2016 13:57:04 +0100 Subject: [PATCH 106/440] Fix typo (thing --> things) --- docs/patterns/apierrors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/apierrors.rst b/docs/patterns/apierrors.rst index ce5c8446..90e8c13d 100644 --- a/docs/patterns/apierrors.rst +++ b/docs/patterns/apierrors.rst @@ -2,7 +2,7 @@ Implementing API Exceptions =========================== It's very common to implement RESTful APIs on top of Flask. One of the -first thing that developers run into is the realization that the builtin +first things that developers run into is the realization that the builtin exceptions are not expressive enough for APIs and that the content type of :mimetype:`text/html` they are emitting is not very useful for API consumers. From 317d60307d7310f0987951164c2c70098b8b5749 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 11 Apr 2016 16:10:51 -0700 Subject: [PATCH 107/440] fix some warnings while building docs --- docs/conf.py | 29 +++++++++++++++-------------- docs/extensions.rst | 2 ++ docs/upgrading.rst | 8 ++++---- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 285c8981..880e9122 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -10,7 +10,7 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - +from __future__ import print_function import sys, os # If extensions (or modules to document with autodoc) are in another directory, @@ -26,8 +26,11 @@ sys.path.append(os.path.abspath('.')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', - 'flaskdocext'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'flaskdocext' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -52,10 +55,8 @@ import pkg_resources try: release = pkg_resources.get_distribution('Flask').version except pkg_resources.DistributionNotFound: - print 'To build the documentation, The distribution information of Flask' - print 'Has to be available. Either install the package into your' - print 'development environment or run "setup.py develop" to setup the' - print 'metadata. A virtualenv is recommended!' + print('Flask must be installed to build the documentation.') + print('Install from source using `pip install -e .` in a virtualenv.') sys.exit(1) del pkg_resources @@ -258,13 +259,13 @@ pygments_style = 'flask_theme_support.FlaskyStyle' # fall back if theme is not there try: __import__('flask_theme_support') -except ImportError, e: - print '-' * 74 - print 'Warning: Flask themes unavailable. Building with default theme' - print 'If you want the Flask themes, run this command and build again:' - print - print ' git submodule update --init' - print '-' * 74 +except ImportError as e: + print('-' * 74) + print('Warning: Flask themes unavailable. Building with default theme') + print('If you want the Flask themes, run this command and build again:') + print() + print(' git submodule update --init') + print('-' * 74) pygments_style = 'tango' html_theme = 'default' diff --git a/docs/extensions.rst b/docs/extensions.rst index c9af3035..d1d24807 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -1,3 +1,5 @@ +.. _extensions: + Flask Extensions ================ diff --git a/docs/upgrading.rst b/docs/upgrading.rst index e89e8765..eede3f6e 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -25,7 +25,7 @@ Version 1.0 ----------- Debugging -+++++++++ +````````` Flask 1.0 removed the ``debug_log_format`` attribute from Flask applications. Instead the new ``LOGGER_HANDLER_POLICY`` configuration can @@ -33,7 +33,7 @@ be used to disable the default log handlers and custom log handlers can be set up. Error handling -++++++++++++++ +`````````````` The behavior of error handlers was changed. The precedence of handlers used to be based on the decoration/call order of @@ -53,14 +53,14 @@ Trying to register a handler on an instance now raises :exc:`ValueError`. handlers only using exception classes and HTTP error codes. Templating -++++++++++ +`````````` The :func:`~flask.templating.render_template_string` function has changed to autoescape template variables by default. This better matches the behavior of :func:`~flask.templating.render_template`. Extension imports -+++++++++++++++++ +````````````````` Extension imports of the form ``flask.ext.foo`` are deprecated, you should use ``flask_foo``. From 1cc9ccfc671220ff69d6967de0fb71b18a68ad38 Mon Sep 17 00:00:00 2001 From: Corey Goldberg Date: Tue, 3 May 2016 11:55:36 -0400 Subject: [PATCH 108/440] update docs and refer to setuptools --- docs/patterns/distribute.rst | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index 802c89a0..a234e4d9 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -4,10 +4,9 @@ Deploying with Setuptools ========================= `Setuptools`_, is an extension library that is commonly used to -(like the name says) distribute Python libraries and -extensions. It extends distutils, a basic module installation system -shipped with Python to also support various more complex constructs that -make larger applications easier to distribute: +distribute Python libraries and extensions. It extends distutils, a basic +module installation system shipped with Python to also support various more +complex constructs that make larger applications easier to distribute: - **support for dependencies**: a library or application can declare a list of other libraries it depends on which will be installed @@ -16,15 +15,15 @@ make larger applications easier to distribute: Python installation. This makes it possible to query information provided by one package from another package. The best known feature of this system is the entry point support which allows one package to - declare an "entry point" another package can hook into to extend the + declare an "entry point" that another package can hook into to extend the other package. -- **installation manager**: :command:`easy_install`, which comes with distribute - can install other libraries for you. You can also use `pip`_ which +- **installation manager**: :command:`easy_install`, which comes with setuptools + can install other libraries for you. You can also use :command:`pip`_ which sooner or later will replace :command:`easy_install` which does more than just installing packages for you. -Flask itself, and all the libraries you can find on the cheeseshop -are distributed with either distribute, the older setuptools or distutils. +Flask itself, and all the libraries you can find on PyPI are distributed with +either setuptools or distutils. In this case we assume your application is called :file:`yourapplication.py` and you are not using a module, but a :ref:`package @@ -32,7 +31,7 @@ In this case we assume your application is called a package, head over to the :ref:`larger-applications` pattern to see how this can be done. -A working deployment with distribute is the first step into more complex +A working deployment with setuptools is the first step into more complex and more automated deployment scenarios. If you want to fully automate the process, also read the :ref:`fabric-deployment` chapter. @@ -66,7 +65,7 @@ A basic :file:`setup.py` file for a Flask application looks like this:: ) Please keep in mind that you have to list subpackages explicitly. If you -want distribute to lookup the packages for you automatically, you can use +want setuptools to lookup the packages for you automatically, you can use the `find_packages` function:: from setuptools import setup, find_packages @@ -78,7 +77,7 @@ the `find_packages` function:: Most parameters to the `setup` function should be self explanatory, `include_package_data` and `zip_safe` might not be. -`include_package_data` tells distribute to look for a :file:`MANIFEST.in` file +`include_package_data` tells setuptools to look for a :file:`MANIFEST.in` file and install all the entries that match as package data. We will use this to distribute the static files and templates along with the Python module (see :ref:`distributing-resources`). The `zip_safe` flag can be used to @@ -94,7 +93,7 @@ Distributing Resources If you try to install the package you just created, you will notice that folders like :file:`static` or :file:`templates` are not installed for you. The -reason for this is that distribute does not know which files to add for +reason for this is that setuptools does not know which files to add for you. What you should do, is to create a :file:`MANIFEST.in` file next to your :file:`setup.py` file. This file lists all the files that should be added to your tarball:: @@ -110,7 +109,7 @@ parameter of the `setup` function to ``True``! Declaring Dependencies ---------------------- -Dependencies are declared in the `install_requires` parameter as list. +Dependencies are declared in the `install_requires` parameter as a list. Each item in that list is the name of a package that should be pulled from PyPI on installation. By default it will always use the most recent version, but you can also provide minimum and maximum version @@ -125,15 +124,15 @@ requirements. Here some examples:: As mentioned earlier, dependencies are pulled from PyPI. What if you want to depend on a package that cannot be found on PyPI and won't be because it is an internal package you don't want to share with anyone? -Just still do as if there was a PyPI entry for it and provide a list of -alternative locations where distribute should look for tarballs:: +Just do it as if there was a PyPI entry and provide a list of +alternative locations where setuptools should look for tarballs:: dependency_links=['http://example.com/yourfiles'] Make sure that page has a directory listing and the links on the page are pointing to the actual tarballs with their correct filenames as this is -how distribute will find the files. If you have an internal company -server that contains the packages, provide the URL to that server there. +how setuptools will find the files. If you have an internal company +server that contains the packages, provide the URL to that server. Installing / Developing From de25e98f917ae441d186e8a722a0f40a6c7458f2 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 4 May 2016 06:46:49 -0700 Subject: [PATCH 109/440] minor rewording of get_json documentation for clarity (#1781) --- flask/wrappers.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index 8b1ca251..9d611504 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -123,11 +123,12 @@ class Request(RequestBase): return False def get_json(self, force=False, silent=False, cache=True): - """Parses the incoming JSON request data and returns it. If - parsing fails the :meth:`on_json_loading_failed` method on the - request object will be invoked. By default this function will - only load the json data if the mimetype is :mimetype:`application/json` - but this can be overridden by the `force` parameter. + """Parses the incoming JSON request data and returns it. By default + this function will return ``None`` if the mimetype is not + :mimetype:`application/json` but this can be overridden by the + ``force`` parameter. If parsing fails the + :meth:`on_json_loading_failed` method on the request object will be + invoked. :param force: if set to ``True`` the mimetype is ignored. :param silent: if set to ``True`` this method will fail silently From 28b36f642ddac117c88c30f4800bc2ec44eddcf9 Mon Sep 17 00:00:00 2001 From: Corey Goldberg Date: Wed, 4 May 2016 21:14:25 -0400 Subject: [PATCH 110/440] removed references to easy_install --- docs/patterns/distribute.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index a234e4d9..872d6e01 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -17,10 +17,11 @@ complex constructs that make larger applications easier to distribute: this system is the entry point support which allows one package to declare an "entry point" that another package can hook into to extend the other package. -- **installation manager**: :command:`easy_install`, which comes with setuptools - can install other libraries for you. You can also use :command:`pip`_ which - sooner or later will replace :command:`easy_install` which does more than just - installing packages for you. +- **installation manager**: :command:`pip` can install other libraries for you. + +If you have Python 2 (>=2.7.9) or Python 3 (>=3.4) installed from python.org, +you will already have pip and setuptools on your system. Otherwise, you +will need to install them yourself. Flask itself, and all the libraries you can find on PyPI are distributed with either setuptools or distutils. @@ -156,5 +157,4 @@ the code without having to run `install` again after each change. .. _pip: https://pypi.python.org/pypi/pip -.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py .. _Setuptools: https://pythonhosted.org/setuptools From e45e17c490add1377cb169bd66e80979d72d5875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20W=C3=B3jcik?= Date: Thu, 5 May 2016 10:27:45 -0700 Subject: [PATCH 111/440] fix a grammar mistake (#1798) --- docs/extensiondev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 9a4cd14c..9119abdb 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -35,7 +35,7 @@ called ``flask_something`` users would import it as ``flask.ext.something``. This is done to transition from the old namespace packages. See :ref:`ext-import-transition` for more details. -But how do extensions look like themselves? An extension has to ensure +But what do extensions look like themselves? An extension has to ensure that it works with multiple Flask application instances at once. This is a requirement because many people will use patterns like the :ref:`app-factories` pattern to create their application as needed to aid From 9f4c569c83f3f00f43a8ea975821c405ab0057f0 Mon Sep 17 00:00:00 2001 From: Benjamin Dopplinger Date: Mon, 9 May 2016 13:37:27 +1000 Subject: [PATCH 112/440] Fix "with" formatting in doc --- flask/testing.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flask/testing.py b/flask/testing.py index 54ce20ef..8eacf58b 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -39,8 +39,9 @@ def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): class FlaskClient(Client): """Works like a regular Werkzeug test client but has some knowledge about how Flask works to defer the cleanup of the request context stack to the - end of a with body when used in a with statement. For general information - about how to use this class refer to :class:`werkzeug.test.Client`. + end of a ``with`` body when used in a ``with`` statement. For general + information about how to use this class refer to + :class:`werkzeug.test.Client`. Basic usage is outlined in the :ref:`testing` chapter. """ @@ -49,9 +50,9 @@ class FlaskClient(Client): @contextmanager def session_transaction(self, *args, **kwargs): - """When used in combination with a with statement this opens a + """When used in combination with a ``with`` statement this opens a session transaction. This can be used to modify the session that - the test client uses. Once the with block is left the session is + the test client uses. Once the ``with`` block is left the session is stored back. :: From 88500f5cc709e2e931e6547ed9b58033a50215a8 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 16 May 2016 19:36:55 +0200 Subject: [PATCH 113/440] Forward ported CLI tests from Flask-CLI and fixed a bug with the CLI's name. (#1806) * Forward port the CLI tests from Flask-CLI. * Make sure the parameter passed to the CLI's AppGroup is the app's name, not the app itself. --- flask/app.py | 2 +- tests/test_apps/cliapp/__init__.py | 0 tests/test_apps/cliapp/app.py | 5 ++ tests/test_apps/cliapp/multiapp.py | 6 ++ tests/test_cli.py | 137 +++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/test_apps/cliapp/__init__.py create mode 100644 tests/test_apps/cliapp/app.py create mode 100644 tests/test_apps/cliapp/multiapp.py create mode 100644 tests/test_cli.py diff --git a/flask/app.py b/flask/app.py index 6a539106..0dbee5e0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -546,7 +546,7 @@ class Flask(_PackageBoundObject): #: provided by Flask itself and can be overridden. #: #: This is an instance of a :class:`click.Group` object. - self.cli = cli.AppGroup(self) + self.cli = cli.AppGroup(self.name) def _get_error_handlers(self): from warnings import warn diff --git a/tests/test_apps/cliapp/__init__.py b/tests/test_apps/cliapp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_apps/cliapp/app.py b/tests/test_apps/cliapp/app.py new file mode 100644 index 00000000..f142bc47 --- /dev/null +++ b/tests/test_apps/cliapp/app.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import, print_function + +from flask import Flask + +testapp = Flask('testapp') diff --git a/tests/test_apps/cliapp/multiapp.py b/tests/test_apps/cliapp/multiapp.py new file mode 100644 index 00000000..67ed6fba --- /dev/null +++ b/tests/test_apps/cliapp/multiapp.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, print_function + +from flask import Flask + +app1 = Flask('app1') +app2 = Flask('app2') diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..1270c760 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +""" + tests.test_cli + ~~~~~~~~~~~~~~ + + :copyright: (c) 2016 by the Flask Team, see AUTHORS for more details. + :license: BSD, see LICENSE for more details. +""" +# +# This file was part of Flask-CLI and was modified under the terms its license, +# the Revised BSD License. +# Copyright (C) 2015 CERN. +# +from __future__ import absolute_import, print_function + +import click +import pytest +from click.testing import CliRunner +from flask import Flask, current_app + +from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ + find_best_app, locate_app, script_info_option, with_appcontext + + +def test_cli_name(test_apps): + "Make sure the CLI object's name is the app's name and not the app itself" + from cliapp.app import testapp + assert testapp.cli.name == testapp.name + + +def test_find_best_app(test_apps): + """Test of find_best_app.""" + class mod: + app = Flask('appname') + assert find_best_app(mod) == mod.app + + class mod: + application = Flask('appname') + assert find_best_app(mod) == mod.application + + class mod: + myapp = Flask('appname') + assert find_best_app(mod) == mod.myapp + + class mod: + myapp = Flask('appname') + myapp2 = Flask('appname2') + + pytest.raises(NoAppException, find_best_app, mod) + + +def test_locate_app(test_apps): + """Test of locate_app.""" + assert locate_app("cliapp.app").name == "testapp" + assert locate_app("cliapp.app:testapp").name == "testapp" + assert locate_app("cliapp.multiapp:app1").name == "app1" + pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") + + +def test_scriptinfo(test_apps): + """Test of ScriptInfo.""" + obj = ScriptInfo(app_import_path="cliapp.app:testapp") + assert obj.load_app().name == "testapp" + assert obj.load_app().name == "testapp" + + def create_app(info): + return Flask("createapp") + + obj = ScriptInfo(create_app=create_app) + app = obj.load_app() + assert app.name == "createapp" + assert obj.load_app() == app + + +def test_with_appcontext(): + """Test of with_appcontext.""" + @click.command() + @with_appcontext + def testcmd(): + click.echo(current_app.name) + + obj = ScriptInfo(create_app=lambda info: Flask("testapp")) + + runner = CliRunner() + result = runner.invoke(testcmd, obj=obj) + assert result.exit_code == 0 + assert result.output == 'testapp\n' + + +def test_appgroup(): + """Test of with_appcontext.""" + @click.group(cls=AppGroup) + def cli(): + pass + + @cli.command(with_appcontext=True) + def test(): + click.echo(current_app.name) + + @cli.group() + def subgroup(): + pass + + @subgroup.command(with_appcontext=True) + def test2(): + click.echo(current_app.name) + + obj = ScriptInfo(create_app=lambda info: Flask("testappgroup")) + + runner = CliRunner() + result = runner.invoke(cli, ['test'], obj=obj) + assert result.exit_code == 0 + assert result.output == 'testappgroup\n' + + result = runner.invoke(cli, ['subgroup', 'test2'], obj=obj) + assert result.exit_code == 0 + assert result.output == 'testappgroup\n' + + +def test_flaskgroup(): + """Test FlaskGroup.""" + def create_app(info): + return Flask("flaskgroup") + + @click.group(cls=FlaskGroup, create_app=create_app) + @script_info_option('--config', script_info_key='config') + def cli(**params): + pass + + @cli.command() + def test(): + click.echo(current_app.name) + + runner = CliRunner() + result = runner.invoke(cli, ['test']) + assert result.exit_code == 0 + assert result.output == 'flaskgroup\n' From c810fae9e835ad446e7e9d61c12bbf2f6b597109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 20 May 2016 21:57:10 +0200 Subject: [PATCH 114/440] turn 2 prints to py2/py3 compatible syntax (#1812) --- scripts/flaskext_tester.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/flaskext_tester.py b/scripts/flaskext_tester.py index 45adb0a4..93ab0ad7 100644 --- a/scripts/flaskext_tester.py +++ b/scripts/flaskext_tester.py @@ -112,7 +112,7 @@ RESULT_TEMPATE = u'''\ def log(msg, *args): - print '[EXTTEST]', msg % args + print('[EXTTEST] ' + (msg % args)) class TestResult(object): @@ -302,7 +302,7 @@ def main(): if args.browse: import webbrowser webbrowser.open('file:///' + filename.lstrip('/')) - print 'Results written to', filename + print('Results written to {}'.format(filename)) if __name__ == '__main__': From 883cb7cedcc23efbb7e73f8884df8f406e452541 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 22 May 2016 10:34:48 +0200 Subject: [PATCH 115/440] Always run gc before leak test --- tests/test_regression.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_regression.py b/tests/test_regression.py index 1497750d..ffeebf86 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -39,8 +39,7 @@ class assert_no_leak(object): self.old_objects = len(gc.get_objects()) def __exit__(self, exc_type, exc_value, tb): - if not hasattr(sys, 'getrefcount'): - gc.collect() + gc.collect() new_objects = len(gc.get_objects()) if new_objects > self.old_objects: pytest.fail('Example code leaked') From 6aee9f6d77e0696bd570d6095445d73c48539f8a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 22 May 2016 10:45:25 +0200 Subject: [PATCH 116/440] Resolve state issue for url_for with forced scheme This fixes #1596 --- flask/helpers.py | 13 +++++++++++-- tests/test_helpers.py | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 02e99e37..a0606bf2 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -300,14 +300,23 @@ def url_for(endpoint, **values): scheme = values.pop('_scheme', None) appctx.app.inject_url_defaults(endpoint, values) + # This is not the best way to deal with this but currently the + # underlying Werkzeug router does not support overriding the scheme on + # a per build call basis. + old_scheme = None if scheme is not None: if not external: raise ValueError('When specifying _scheme, _external must be True') + old_scheme = url_adapter.url_scheme url_adapter.url_scheme = scheme try: - rv = url_adapter.build(endpoint, values, method=method, - force_external=external) + try: + rv = url_adapter.build(endpoint, values, method=method, + force_external=external) + finally: + if old_scheme is not None: + url_adapter.url_scheme = old_scheme except BuildError as error: # We need to inject the values again so that the app callback can # deal with that sort of stuff. diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 5605c45d..6dc41fad 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -627,6 +627,16 @@ class TestLogging(object): 'index', _scheme='https') + def test_url_for_with_alternating_schemes(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return '42' + with app.test_request_context(): + assert flask.url_for('index', _external=True) == 'http://localhost/' + assert flask.url_for('index', _external=True, _scheme='https') == 'https://localhost/' + assert flask.url_for('index', _external=True) == 'http://localhost/' + def test_url_with_method(self): from flask.views import MethodView app = flask.Flask(__name__) From 96ec24f6e0ff77a24d0151afe20ec7c97118019f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 22 May 2016 11:36:40 +0200 Subject: [PATCH 117/440] Fast path for disabled template load explain. --- flask/templating.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index 8c95a6a7..2da4926d 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -52,27 +52,36 @@ class DispatchingJinjaLoader(BaseLoader): self.app = app def get_source(self, environment, template): - explain = self.app.config['EXPLAIN_TEMPLATE_LOADING'] + if self.app.config['EXPLAIN_TEMPLATE_LOADING']: + return self._get_source_explained(environment, template) + return self._get_source_fast(environment, template) + + def _get_source_explained(self, environment, template): attempts = [] - tmplrv = None + trv = None for srcobj, loader in self._iter_loaders(template): try: rv = loader.get_source(environment, template) - if tmplrv is None: - tmplrv = rv - if not explain: - break + if trv is None: + trv = rv except TemplateNotFound: rv = None attempts.append((loader, srcobj, rv)) - if explain: - from .debughelpers import explain_template_loading_attempts - explain_template_loading_attempts(self.app, template, attempts) + from .debughelpers import explain_template_loading_attempts + explain_template_loading_attempts(self.app, template, attempts) + + if trv is not None: + return trv + raise TemplateNotFound(template) - if tmplrv is not None: - return tmplrv + def _get_source_fast(self, environment, template): + for srcobj, loader in self._iter_loaders(template): + try: + return loader.get_source(environment, template) + except TemplateNotFound: + continue raise TemplateNotFound(template) def _iter_loaders(self, template): From bdbca923ef26424f605b4c744131512a5edb0f1e Mon Sep 17 00:00:00 2001 From: Thomas Sanjurjo Date: Sun, 22 May 2016 10:09:21 -0400 Subject: [PATCH 118/440] Addressing Issue 1809 (#1811) document kwargs for Flask.register_blueprint --- flask/app.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 0dbee5e0..f4e953ea 100644 --- a/flask/app.py +++ b/flask/app.py @@ -934,7 +934,22 @@ class Flask(_PackageBoundObject): @setupmethod def register_blueprint(self, blueprint, **options): - """Registers a blueprint on the application. + """Register a blueprint on the application. For information about + blueprints head over to :ref:`blueprints`. + + The blueprint name is passed in as the first argument. + Options are passed as additional keyword arguments and forwarded to + `blueprints` in an "options" dictionary. + + :param subdomain: set a subdomain for the blueprint + :param url_prefix: set the prefix for all URLs defined on the blueprint. + ``(url_prefix='/')`` + :param url_defaults: a dictionary with URL defaults that is added to + each and every URL defined with this blueprint + :param static_folder: add a static folder to urls in this blueprint + :param static_url_path: add a static url path to urls in this blueprint + :param template_folder: set an alternate template folder + :param root_path: set an alternate root path for this blueprint .. versionadded:: 0.7 """ From c5900a1adf8e868eca745225f3cf32218cdbbb23 Mon Sep 17 00:00:00 2001 From: ThiefMaster Date: Mon, 23 May 2016 14:42:42 +0200 Subject: [PATCH 119/440] s/1.0/0.11/ in versionadded/versionchanged markers closes #1817 --- docs/cli.rst | 2 +- docs/config.rst | 2 +- docs/errorhandling.rst | 2 +- flask/app.py | 18 +++++++++--------- flask/blueprints.py | 2 +- flask/config.py | 6 +++--- flask/json.py | 2 +- flask/sessions.py | 2 +- flask/wrappers.py | 2 +- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 141176ce..d3269c57 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -3,7 +3,7 @@ Command Line Interface ====================== -.. versionadded:: 1.0 +.. versionadded:: 0.11 .. currentmodule:: flask diff --git a/docs/config.rst b/docs/config.rst index 1d9445d3..3039b3ea 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -241,7 +241,7 @@ The following configuration values are used internally by Flask: .. versionadded:: 0.10 ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR`` -.. versionadded:: 1.0 +.. versionadded:: 0.11 ``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``, ``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING`` diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 1e6a771f..af593531 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -63,7 +63,7 @@ You are however not limited to :exc:`~werkzeug.exceptions.HTTPException` or HTTP status codes but can register a handler for every exception class you like. -.. versionchanged:: 1.0 +.. versionchanged:: 0.11 Errorhandlers are now prioritized by specificity of the exception classes they are registered for instead of the order they are registered in. diff --git a/flask/app.py b/flask/app.py index f4e953ea..416fcf08 100644 --- a/flask/app.py +++ b/flask/app.py @@ -120,7 +120,7 @@ class Flask(_PackageBoundObject): The `instance_path` and `instance_relative_config` parameters were added. - .. versionadded:: 1.0 + .. versionadded:: 0.11 The `root_path` parameter was added. :param import_name: the name of the application package @@ -159,7 +159,7 @@ class Flask(_PackageBoundObject): #: The class that is used for the Jinja environment. #: - #: .. versionadded:: 1.0 + #: .. versionadded:: 0.11 jinja_environment = Environment #: The class that is used for the :data:`~flask.g` instance. @@ -198,7 +198,7 @@ class Flask(_PackageBoundObject): #: 1. Default values for certain config options. #: 2. Access to config values through attributes in addition to keys. #: - #: .. versionadded:: 1.0 + #: .. versionadded:: 0.11 config_class = Config #: The debug flag. Set this to ``True`` to enable debugging of the @@ -481,7 +481,7 @@ class Flask(_PackageBoundObject): #: A list of shell context processor functions that should be run #: when a shell context is created. #: - #: .. versionadded:: 1.0 + #: .. versionadded:: 0.11 self.shell_context_processors = [] #: all the attached blueprints in a dictionary by name. Blueprints @@ -683,7 +683,7 @@ class Flask(_PackageBoundObject): this function to customize the behavior. .. versionadded:: 0.5 - .. versionchanged:: 1.0 + .. versionchanged:: 0.11 ``Environment.auto_reload`` set in accordance with ``TEMPLATES_AUTO_RELOAD`` configuration option. """ @@ -772,7 +772,7 @@ class Flask(_PackageBoundObject): application. This runs all the registered shell context processors. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ rv = {'app': self, 'g': g} for processor in self.shell_context_processors: @@ -893,7 +893,7 @@ class Flask(_PackageBoundObject): to override the client to be used by setting the :attr:`test_client_class` attribute. - .. versionchanged:: 1.0 + .. versionchanged:: 0.11 Added `**kwargs` to support passing additional keyword arguments to the constructor of :attr:`test_client_class`. """ @@ -969,7 +969,7 @@ class Flask(_PackageBoundObject): def iter_blueprints(self): """Iterates over all blueprints by the order they were registered. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ return iter(self._blueprint_order) @@ -1418,7 +1418,7 @@ class Flask(_PackageBoundObject): def shell_context_processor(self, f): """Registers a shell context processor function. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ self.shell_context_processors.append(f) return f diff --git a/flask/blueprints.py b/flask/blueprints.py index c0d47476..586a1b0b 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -407,7 +407,7 @@ class Blueprint(_PackageBoundObject): application-wide function of the :class:`~flask.Flask` object but for error handlers limited to this blueprint. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ self.record_once(lambda s: s.app._register_error_handler( self.name, code_or_exception, f)) diff --git a/flask/config.py b/flask/config.py index 6f643a99..426a23a2 100644 --- a/flask/config.py +++ b/flask/config.py @@ -176,7 +176,7 @@ class Config(dict): :param silent: set to ``True`` if you want silent failure for missing files. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ filename = os.path.join(self.root_path, filename) @@ -194,7 +194,7 @@ class Config(dict): """Updates the config like :meth:`update` ignoring items with non-upper keys. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ mappings = [] if len(mapping) == 1: @@ -239,7 +239,7 @@ class Config(dict): :param trim_namespace: a flag indicating if the keys of the resulting dictionary should not include the namespace - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ rv = {} for k, v in iteritems(self): diff --git a/flask/json.py b/flask/json.py index 2bd47902..b9ce4a08 100644 --- a/flask/json.py +++ b/flask/json.py @@ -235,7 +235,7 @@ def jsonify(*args, **kwargs): } - .. versionchanged:: 1.0 + .. versionchanged:: 0.11 Added support for serializing top-level arrays. This introduces a security risk in ancient browsers. See :ref:`json-security` for details. diff --git a/flask/sessions.py b/flask/sessions.py index 48fd08fa..b9120712 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -263,7 +263,7 @@ class SessionInterface(object): This check is usually skipped if sessions get deleted. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ if session.modified: return True diff --git a/flask/wrappers.py b/flask/wrappers.py index 9d611504..d1d7ba7d 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -113,7 +113,7 @@ class Request(RequestBase): is considered to include JSON data if the mimetype is :mimetype:`application/json` or :mimetype:`application/*+json`. - .. versionadded:: 1.0 + .. versionadded:: 0.11 """ mt = self.mimetype if mt == 'application/json': From 92f63a1c1d483c1a1636efb20cadb1c2932b33ff Mon Sep 17 00:00:00 2001 From: dataforger Date: Tue, 24 May 2016 15:06:34 -0400 Subject: [PATCH 120/440] fix docstring (#1818) change string to docstring --- tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 1270c760..5a871e32 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,7 +23,7 @@ from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ def test_cli_name(test_apps): - "Make sure the CLI object's name is the app's name and not the app itself" + """Make sure the CLI object's name is the app's name and not the app itself""" from cliapp.app import testapp assert testapp.cli.name == testapp.name From 523e27118359425048541d92892f20ee048c0b76 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 20:07:52 +0200 Subject: [PATCH 121/440] Implemented simplified CLI interface --- Makefile | 2 +- flask/app.py | 5 +- flask/cli.py | 124 +++++++++++++++------------------------------- flask/helpers.py | 8 ++- tests/test_cli.py | 3 +- 5 files changed, 51 insertions(+), 91 deletions(-) diff --git a/Makefile b/Makefile index 1268a1b4..350aa9a4 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: clean-pyc test test: - py.test tests examples + FLASK_DEBUG= py.test tests examples tox-test: tox diff --git a/flask/app.py b/flask/app.py index 416fcf08..b1ea0464 100644 --- a/flask/app.py +++ b/flask/app.py @@ -22,7 +22,8 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed, BadRequest, default_exceptions from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ - locked_cached_property, _endpoint_from_view_func, find_package + locked_cached_property, _endpoint_from_view_func, find_package, \ + get_debug_flag from . import json, cli from .wrappers import Request, Response from .config import ConfigAttribute, Config @@ -289,7 +290,7 @@ class Flask(_PackageBoundObject): #: Default configuration parameters. default_config = ImmutableDict({ - 'DEBUG': False, + 'DEBUG': get_debug_flag(default=False), 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, diff --git a/flask/cli.py b/flask/cli.py index 141c9eea..e873c108 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -17,6 +17,7 @@ from functools import update_wrapper import click from ._compat import iteritems, reraise +from .helpers import get_debug_flag class NoAppException(click.UsageError): @@ -98,6 +99,15 @@ def locate_app(app_id): return app +def find_default_import_path(): + app = os.environ.get('FLASK_APP') + if app is None: + return + if os.path.isfile(app): + return prepare_exec_for_file(app) + return app + + class DispatchingApp(object): """Special application that dispatches to a flask application which is imported by name in a background thread. If an error happens @@ -158,12 +168,13 @@ class ScriptInfo(object): to click. """ - def __init__(self, app_import_path=None, debug=None, create_app=None): - #: The application import path - self.app_import_path = app_import_path - #: The debug flag. If this is not None, the application will - #: automatically have it's debug flag overridden with this value. - self.debug = debug + def __init__(self, app_import_path=None, create_app=None): + if create_app is None: + if app_import_path is None: + app_import_path = find_default_import_path() + self.app_import_path = app_import_path + else: + self.app_import_path = None #: Optionally a function that is passed the script info to create #: the instance of the application. self.create_app = create_app @@ -185,11 +196,12 @@ class ScriptInfo(object): else: if self.app_import_path is None: raise NoAppException('Could not locate Flask application. ' - 'You did not provide FLASK_APP or the ' - '--app parameter.') + 'You did not provide the FLASK_APP ' + 'environment variable.') rv = locate_app(self.app_import_path) - if self.debug is not None: - rv.debug = self.debug + debug = get_debug_flag() + if debug is not None: + rv.debug = debug self._loaded_app = rv return rv @@ -210,29 +222,6 @@ def with_appcontext(f): return update_wrapper(decorator, f) -def set_debug_value(ctx, param, value): - ctx.ensure_object(ScriptInfo).debug = value - - -def set_app_value(ctx, param, value): - if value is not None: - if os.path.isfile(value): - value = prepare_exec_for_file(value) - elif '.' not in sys.path: - sys.path.insert(0, '.') - ctx.ensure_object(ScriptInfo).app_import_path = value - - -debug_option = click.Option(['--debug/--no-debug'], - help='Enable or disable debug mode.', - default=None, callback=set_debug_value) - - -app_option = click.Option(['-a', '--app'], - help='The application to run', - callback=set_app_value, is_eager=True) - - class AppGroup(click.Group): """This works similar to a regular click :class:`~click.Group` but it changes the behavior of the :meth:`command` decorator so that it @@ -273,25 +262,12 @@ class FlaskGroup(AppGroup): :param add_default_commands: if this is True then the default run and shell commands wil be added. - :param add_app_option: adds the default :option:`--app` option. This gets - automatically disabled if a `create_app` - callback is defined. - :param add_debug_option: adds the default :option:`--debug` option. :param create_app: an optional callback that is passed the script info and returns the loaded app. """ - def __init__(self, add_default_commands=True, add_app_option=None, - add_debug_option=True, create_app=None, **extra): - params = list(extra.pop('params', None) or ()) - if add_app_option is None: - add_app_option = create_app is None - if add_app_option: - params.append(app_option) - if add_debug_option: - params.append(debug_option) - - AppGroup.__init__(self, params=params, **extra) + def __init__(self, add_default_commands=True, create_app=None, **extra): + AppGroup.__init__(self, **extra) self.create_app = create_app if add_default_commands: @@ -342,33 +318,6 @@ class FlaskGroup(AppGroup): return AppGroup.main(self, *args, **kwargs) -def script_info_option(*args, **kwargs): - """This decorator works exactly like :func:`click.option` but is eager - by default and stores the value in the :attr:`ScriptInfo.data`. This - is useful to further customize an application factory in very complex - situations. - - :param script_info_key: this is a mandatory keyword argument which - defines under which data key the value should - be stored. - """ - try: - key = kwargs.pop('script_info_key') - except LookupError: - raise TypeError('script_info_key not provided.') - - real_callback = kwargs.get('callback') - def callback(ctx, param, value): - if real_callback is not None: - value = real_callback(ctx, value) - ctx.ensure_object(ScriptInfo).data[key] = value - return value - - kwargs['callback'] = callback - kwargs.setdefault('is_eager', True) - return click.option(*args, **kwargs) - - @click.command('run', short_help='Runs a development server.') @click.option('--host', '-h', default='127.0.0.1', help='The interface to bind to.') @@ -400,10 +349,12 @@ def run_command(info, host, port, reload, debugger, eager_loading, Flask is enabled and disabled otherwise. """ from werkzeug.serving import run_simple + + debug = get_debug_flag() if reload is None: - reload = info.debug + reload = bool(debug) if debugger is None: - debugger = info.debug + debugger = bool(debug) if eager_loading is None: eager_loading = not reload @@ -418,12 +369,9 @@ def run_command(info, host, port, reload, debugger, eager_loading, # we won't print anything. if info.app_import_path is not None: print(' * Serving Flask app "%s"' % info.app_import_path) - if info.debug is not None: - print(' * Forcing debug %s' % (info.debug and 'on' or 'off')) run_simple(host, port, app, use_reloader=reload, - use_debugger=debugger, threaded=with_threads, - passthrough_errors=True) + use_debugger=debugger, threaded=with_threads) @click.command('shell', short_help='Runs a shell in the app context.') @@ -464,15 +412,21 @@ cli = FlaskGroup(help="""\ This shell command acts as general utility script for Flask applications. It loads the application configured (either through the FLASK_APP environment -variable or the --app parameter) and then provides commands either provided -by the application or Flask itself. +variable) and then provides commands either provided by the application or +Flask itself. The most useful commands are the "run" and "shell" command. Example usage: - flask --app=hello --debug run -""") +\b + %(prefix)s%(cmd)s FLASK_APP=hello + %(prefix)s%(cmd)s FLASK_DEBUG=1 + %(prefix)sflask run +""" % { + 'cmd': os.name == 'posix' and 'export' or 'set', + 'prefix': os.name == 'posix' and '$ ' or '', +}) def main(as_module=False): diff --git a/flask/helpers.py b/flask/helpers.py index 02e99e37..e8007b55 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,7 +14,6 @@ import sys import pkgutil import posixpath import mimetypes -from datetime import timedelta from time import time from zlib import adler32 from threading import RLock @@ -54,6 +53,13 @@ _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): + val = os.environ.get('FLASK_DEBUG') + if not val: + return default + return val not in ('0', 'false', 'no') + + def _endpoint_from_view_func(view_func): """Internal helper that returns the default endpoint for a given function. This always is the function name. diff --git a/tests/test_cli.py b/tests/test_cli.py index 5a871e32..0a479857 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -19,7 +19,7 @@ from click.testing import CliRunner from flask import Flask, current_app from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ - find_best_app, locate_app, script_info_option, with_appcontext + find_best_app, locate_app, with_appcontext def test_cli_name(test_apps): @@ -123,7 +123,6 @@ def test_flaskgroup(): return Flask("flaskgroup") @click.group(cls=FlaskGroup, create_app=create_app) - @script_info_option('--config', script_info_key='config') def cli(**params): pass From a7d829c61854bc609a343eec114d2a129761fe9f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 20:45:50 +0200 Subject: [PATCH 122/440] Update docs to the new CLI patterns --- docs/api.rst | 7 -- docs/cli.rst | 141 ++++++++++++--------------------- docs/patterns/appfactories.rst | 3 +- docs/quickstart.rst | 44 +++++----- docs/tutorial/dbcon.rst | 1 - docs/tutorial/dbinit.rst | 2 +- docs/tutorial/setup.rst | 13 +-- examples/flaskr/README | 12 ++- examples/minitwit/README | 8 +- flask/cli.py | 22 +++-- 10 files changed, 112 insertions(+), 141 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 9d9d3b1a..688b6811 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -892,13 +892,6 @@ Command Line Interface Marks a function so that an instance of :class:`ScriptInfo` is passed as first argument to the click callback. -.. autofunction:: script_info_option - - A special decorator that informs a click callback to be passed the - script info object as first argument. This is normally not useful - unless you implement very special commands like the run command which - does not want the application to be loaded yet. - .. autodata:: run_command .. autodata:: shell_command diff --git a/docs/cli.rst b/docs/cli.rst index d3269c57..72184b0a 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -15,41 +15,38 @@ applications. Basic Usage ----------- -After installation of Flask you will now find a :command:`flask` script installed -into your virtualenv. If you don't want to install Flask or you have a -special use-case you can also use ``python -m flask`` to accomplish exactly -the same. +After installation of Flask you will now find a :command:`flask` script +installed into your virtualenv. If you don't want to install Flask or you +have a special use-case you can also use ``python -m flask`` to accomplish +exactly the same. The way this script works is by providing access to all the commands on your Flask application's :attr:`Flask.cli` instance as well as some built-in commands that are always there. Flask extensions can also register more commands there if they desire so. -For the :command:`flask` script to work, an application needs to be discovered. -The two most common ways are either an environment variable -(``FLASK_APP``) or the :option:`--app` / :option:`-a` parameter. It should be the -import path for your application or the path to a Python file. In the -latter case Flask will attempt to setup the Python path for you -automatically and discover the module name but that might not always work. +For the :command:`flask` script to work, an application needs to be +discovered. This is achieved by exporting the ``FLASK_APP`` environment +variable. It can be either set to an import path or to a filename of a +Python module that contains a Flask application. In that imported file the name of the app needs to be called ``app`` or -optionally be specified after a colon. +optionally be specified after a colon. For instance +`mymodule:application` would tell it to use the `application` object in +the :file:`mymodule.py` file. -Given a :file:`hello.py` file with the application in it named ``app`` this is -how it can be run. +Given a :file:`hello.py` file with the application in it named ``app`` +this is how it can be run. Environment variables (On Windows use ``set`` instead of ``export``):: export FLASK_APP=hello flask run -Parameters:: +Or with a filename:: - flask --app=hello run - -File names:: - - flask --app=hello.py run + export FLASK_APP=/path/to/hello.py + flask run Virtualenv Integration ---------------------- @@ -62,16 +59,20 @@ automatically also activate the correct application name. Debug Flag ---------- -The :command:`flask` script can be run with :option:`--debug` or :option:`--no-debug` to -automatically flip the debug flag of the application. This can also be -configured by setting ``FLASK_DEBUG`` to ``1`` or ``0``. +The :command:`flask` script can also be instructed to enable the debug +mode of the application automatically by exporting ``FLASK_DEBUG``. If +set to ``1`` debug is enabled or ``0`` disables it. + +Or with a filename:: + + export FLASK_DEBUG=1 Running a Shell --------------- To run an interactive Python shell you can use the ``shell`` command:: - flask --app=hello shell + flask shell This will start up an interactive Python shell, setup the correct application context and setup the local variables in the shell. This is @@ -86,6 +87,7 @@ easily. Flask uses `click`_ for the command interface which makes creating custom commands very easy. For instance if you want a shell command to initialize the database you can do this:: + import click from flask import Flask app = Flask(__name__) @@ -93,11 +95,11 @@ command to initialize the database you can do this:: @app.cli.command() def initdb(): """Initialize the database.""" - print 'Init the db' + click.echo('Init the db') The command will then show up on the command line:: - $ flask -a hello.py initdb + $ flask initdb Init the db Application Context @@ -122,12 +124,12 @@ Factory Functions ----------------- In case you are using factory functions to create your application (see -:ref:`app-factories`) you will discover that the :command:`flask` command cannot -work with them directly. Flask won't be able to figure out how to +:ref:`app-factories`) you will discover that the :command:`flask` command +cannot work with them directly. Flask won't be able to figure out how to instantiate your application properly by itself. Because of this reason the recommendation is to create a separate file that instantiates -applications. This is by far not the only way to make this work. Another -is the :ref:`custom-scripts` support. +applications. This is not the only way to make this work. Another is the +:ref:`custom-scripts` support. For instance if you have a factory function that creates an application from a filename you could make a separate file that creates such an @@ -152,9 +154,9 @@ From this point onwards :command:`flask` will find your application. Custom Scripts -------------- -While the most common way is to use the :command:`flask` command, you can also -make your own "driver scripts". Since Flask uses click for the scripts -there is no reason you cannot hook these scripts into any click +While the most common way is to use the :command:`flask` command, you can +also make your own "driver scripts". Since Flask uses click for the +scripts there is no reason you cannot hook these scripts into any click application. There is one big caveat and that is, that commands registered to :attr:`Flask.cli` will expect to be (indirectly at least) launched from a :class:`flask.cli.FlaskGroup` click group. This is @@ -162,38 +164,32 @@ necessary so that the commands know which Flask application they have to work with. To understand why you might want custom scripts you need to understand how -click finds and executes the Flask application. If you use the :command:`flask` -script you specify the application to work with on the command line or -environment variable as an import name. This is simple but it has some -limitations. Primarily it does not work with application factory -functions (see :ref:`app-factories`). +click finds and executes the Flask application. If you use the +:command:`flask` script you specify the application to work with on the +command line or environment variable as an import name. This is simple +but it has some limitations. Primarily it does not work with application +factory functions (see :ref:`app-factories`). With a custom script you don't have this problem as you can fully customize how the application will be created. This is very useful if you write reusable applications that you want to ship to users and they should be presented with a custom management script. -If you are used to writing click applications this will look familiar but -at the same time, slightly different because of how commands are loaded. -We won't go into detail now about the differences but if you are curious -you can have a look at the :ref:`script-info-object` section to learn all -about it. - To explain all of this, here is an example :file:`manage.py` script that manages a hypothetical wiki application. We will go through the details afterwards:: + import os import click - from flask.cli import FlaskGroup, script_info_option + from flask.cli import FlaskGroup def create_wiki_app(info): from yourwiki import create_app - config = info.data.get('config') or 'wikiconfig.py' - return create_app(config=config) + return create_app( + config=os.environ.get('WIKI_CONFIG', 'wikiconfig.py')) @click.group(cls=FlaskGroup, create_app=create_wiki_app) - @script_info_option('--config', script_info_key='config') - def cli(**params): + def cli(): """This is a management script for the wiki application.""" if __name__ == '__main__': @@ -204,56 +200,17 @@ step. 1. First we import the ``click`` library as well as the click extensions from the ``flask.cli`` package. Primarily we are here interested - in the :class:`~flask.cli.FlaskGroup` click group and the - :func:`~flask.cli.script_info_option` decorator. + in the :class:`~flask.cli.FlaskGroup` click group. 2. The next thing we do is defining a function that is invoked with the - script info object (:ref:`script-info-object`) from Flask and its + script info object (:class:`~flask.cli.ScriptInfo`) from Flask and its purpose is to fully import and create the application. This can either directly import an application object or create it (see - :ref:`app-factories`). - - What is ``info.data``? It's a dictionary of arbitrary data on the - script info that can be filled by options or through other means. We - will come back to this later. + :ref:`app-factories`). In this case we load the config from an + environment variable. 3. Next step is to create a :class:`FlaskGroup`. In this case we just make an empty function with a help doc string that just does nothing and then pass the ``create_wiki_app`` function as a factory function. Whenever click now needs to operate on a Flask application it will call that function with the script info and ask for it to be created. -4. In step 2 you could see that the config is passed to the actual - creation function. This config comes from the :func:`script_info_option` - decorator for the main script. It accepts a :option:`--config` option and - then stores it in the script info so we can use it to create the - application. -5. All is rounded up by invoking the script. - -.. _script-info-object: - -The Script Info ---------------- - -The Flask script integration might be confusing at first, but there is a reason -why it's done this way. The reason for this is that Flask wants to -both provide custom commands to click as well as not loading your -application unless it has to. The reason for this is added flexibility. - -This way an application can provide custom commands, but even in the -absence of an application the :command:`flask` script is still operational on a -basic level. In addition to that it means that the individual commands -have the option to avoid creating an instance of the Flask application -unless required. This is very useful as it allows the server commands for -instance to load the application on a first request instead of -immediately, therefore giving a better debug experience. - -All of this is provided through the :class:`flask.cli.ScriptInfo` object -and some helper utilities around. The basic way it operates is that when -the :class:`flask.cli.FlaskGroup` executes as a script it creates a script -info and keeps it around. From that point onwards modifications on the -script info can be done through click options. To simplify this pattern -the :func:`flask.cli.script_info_option` decorator was added. - -Once Flask actually needs the individual Flask application it will invoke -the :meth:`flask.cli.ScriptInfo.load_app` method. This happens when the -server starts, when the shell is launched or when the script looks for an -application-provided click command. +4. All is rounded up by invoking the script. diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index bcac210c..dc9660ae 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -99,7 +99,8 @@ an application:: It can then be used with the :command:`flask` command:: - flask --app=exampleapp run + export FLASK_APP=exampleapp + flask run Factory Improvements -------------------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 2866b6d7..0ebaf06c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -42,14 +42,20 @@ interpreter. Make sure to not call your application :file:`flask.py` because th would conflict with Flask itself. To run the application you can either use the :command:`flask` command or -python's :option:`-m` switch with Flask:: +python's :option:`-m` switch with Flask. Before you can do that you need +to tell your terminal the application to work with by exporting the +`FLASK_APP` environment variable:: - $ flask -a hello run + $ export FLASK_APP=hello.py + $ flask run * Running on http://127.0.0.1:5000/ -or alternatively:: +If you are on Windows you need to use `set` instead of `export`. - $ python -m flask -a hello run +Alternatively you can use `python -m flask`:: + + $ export FLASK_APP=hello.py + $ python -m flask run * Running on http://127.0.0.1:5000/ This launches a very simple builtin server, which is good enough for testing @@ -72,7 +78,7 @@ should see your hello world greeting. you can make the server publicly available simply by adding ``--host=0.0.0.0`` to the command line:: - flask -a hello run --host=0.0.0.0 + flask run --host=0.0.0.0 This tells your operating system to listen on all public IPs. @@ -87,28 +93,19 @@ to look at the error message. Old Version of Flask ```````````````````` -Versions of Flask older than 1.0 use to have different ways to start the +Versions of Flask older than 0.11 use to have different ways to start the application. In short, the :command:`flask` command did not exist, and neither did ``python -m flask``. In that case you have two options: either upgrade to newer Flask versions or have a look at the :ref:`server` docs to see the alternative method for running a server. -Python older 2.7 -```````````````` - -In case you have a version of Python older than 2.7 ``python -m flask`` -does not work. You can either use :command:`flask` or ``python -m -flask.cli`` as an alternative. This is because Python before 2.7 does no -permit packages to act as executable modules. For more information see -:ref:`cli`. - Invalid Import Name ``````````````````` -The :option:`-a` argument to :command:`flask` is the name of the module to import. In -case that module is incorrectly named you will get an import error upon -start (or if debug is enabled when you navigate to the application). It -will tell you what it tried to import and why it failed. +The :option:`-a` argument to :command:`flask` is the name of the module to +import. In case that module is incorrectly named you will get an import +error upon start (or if debug is enabled when you navigate to the +application). It will tell you what it tried to import and why it failed. The most common reason is a typo or because you did not actually create an ``app`` object. @@ -126,10 +123,13 @@ That is not very nice and Flask can do better. If you enable debug support the server will reload itself on code changes, and it will also provide you with a helpful debugger if things go wrong. -There are different ways to enable the debug mode. The most obvious one -is the :option:`--debug` parameter to the :command:`flask` command:: +To enable debug mode you can export the `FLASK_DEBUG` environment variable +before running the server:: + + $ export FLASK_DEBUG=1 + $ flask run - flask --debug -a hello run +(On Windows you need to use `set` instead of `export`). This does the following things: diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 9a09ff3a..4b5b0915 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -37,7 +37,6 @@ already established connection:: g.sqlite_db = connect_db() return g.sqlite_db - So now we know how to connect, but how do we properly disconnect? For that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext` decorator. It's executed every time the application context tears down:: diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index ebe9ce44..2c26dd1a 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -60,7 +60,7 @@ databases will not commit unless you explicitly tell it to. Now, it is possible to create a database with the :command:`flask` script:: - flask --app=flaskr initdb + flask initdb Initialized the database. .. admonition:: Troubleshooting diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index 703d5504..fef71722 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -91,13 +91,16 @@ tuples. return rv With that out of the way, you should be able to start up the application -without problems. Do this with the following command:: +without problems. Do this with the following commands:: - flask --app=flaskr --debug run + export FLASK_APP=flaskr + export FLASK_DEBUG=1 + flask run -The :option:`--debug` flag enables or disables the interactive debugger. *Never -leave debug mode activated in a production system*, because it will allow -users to execute code on the server! +(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. +*Never leave debug mode activated in a production system*, because it will +allow users to execute code on the server! You will see a message telling you that server has started along with the address at which you can access it. diff --git a/examples/flaskr/README b/examples/flaskr/README index 6259082a..bdf91983 100644 --- a/examples/flaskr/README +++ b/examples/flaskr/README @@ -13,13 +13,17 @@ export an FLASKR_SETTINGS environment variable pointing to a configuration file. - 2. initialize the database with this command: + 2. Instruct flask to use the right application - flask --app=flaskr initdb + export FLASK_APP=flaskr - 3. now you can run flaskr: + 3. initialize the database with this command: - flask --app=flaskr run + flask initdb + + 4. now you can run flaskr: + + flask run the application will greet you on http://localhost:5000/ diff --git a/examples/minitwit/README b/examples/minitwit/README index 92fae233..a2a7f395 100644 --- a/examples/minitwit/README +++ b/examples/minitwit/README @@ -14,13 +14,17 @@ export an MINITWIT_SETTINGS environment variable pointing to a configuration file. + 2. tell flask about the right application: + + export FLASK_APP=minitwit + 2. fire up a shell and run this: - flask --app=minitwit initdb + flask initdb 3. now you can run minitwit: - flask --app=minitwit run + flask run the application will greet you on http://localhost:5000/ diff --git a/flask/cli.py b/flask/cli.py index e873c108..6bd9398b 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -165,7 +165,10 @@ class DispatchingApp(object): class ScriptInfo(object): """Help object to deal with Flask applications. This is usually not necessary to interface with as it's used internally in the dispatching - to click. + to click. In future versions of Flask this object will most likely play + a bigger role. Typically it's created automatically by the + :class:`FlaskGroup` but you can also manually create it and pass it + onwards as click object. """ def __init__(self, app_import_path=None, create_app=None): @@ -174,7 +177,10 @@ class ScriptInfo(object): app_import_path = find_default_import_path() self.app_import_path = app_import_path else: - self.app_import_path = None + app_import_path = None + + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path #: Optionally a function that is passed the script info to create #: the instance of the application. self.create_app = create_app @@ -194,10 +200,12 @@ class ScriptInfo(object): if self.create_app is not None: rv = self.create_app(self) else: - if self.app_import_path is None: - raise NoAppException('Could not locate Flask application. ' - 'You did not provide the FLASK_APP ' - 'environment variable.') + if not self.app_import_path: + raise NoAppException( + 'Could not locate Flask application. You did not provide ' + 'the FLASK_APP environment variable.\n\nFor more ' + 'information see ' + 'http://flask.pocoo.org/docs/latest/quickstart/') rv = locate_app(self.app_import_path) debug = get_debug_flag() if debug is not None: @@ -369,6 +377,8 @@ def run_command(info, host, port, reload, debugger, eager_loading, # 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 1df426aaaacf5dcd774d9c80857c212388d1a038 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 20:48:49 +0200 Subject: [PATCH 123/440] More doc updates for FLASK_APP --- docs/server.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/server.rst b/docs/server.rst index c7a7e641..98bbfdad 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -16,7 +16,9 @@ The :command:`flask` command line script (:ref:`cli`) is strongly recommended fo development because it provides a superior reload experience due to how it loads the application. The basic usage is like this:: - $ flask -a my_application --debug run + $ export FLASK_APP=my_application + $ export FLASK_DEBUG=1 + $ flask run This will enable the debugger, the reloader and then start the server on *http://localhost:5000/*. @@ -25,7 +27,7 @@ The individual features of the server can be controlled by passing more arguments to the ``run`` option. For instance the reloader can be disabled:: - $ flask -a my_application --debug run --no-reload + $ flask run --no-reload In Code ------- @@ -40,11 +42,11 @@ Example:: app.run() This works well for the common case but it does not work well for -development which is why from Flask 1.0 onwards the :command:`flask` method is -recommended. The reason for this is that due to how the reload mechanism -works there are some bizarre side-effects (like executing certain code -twice, sometimes crashing without message or dying when a syntax or -import error happens). +development which is why from Flask 0.11 onwards the :command:`flask` +method is recommended. The reason for this is that due to how the reload +mechanism works there are some bizarre side-effects (like executing +certain code twice, sometimes crashing without message or dying when a +syntax or import error happens). It is however still a perfectly valid method for invoking a non automatic reloading application. From f9ea3fe026a684bb47a0f4794431dab30d0f601e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 20:52:17 +0200 Subject: [PATCH 124/440] 1.0 -> 0.11 in the docs --- docs/api.rst | 2 +- docs/cli.rst | 2 +- docs/errorhandling.rst | 2 +- docs/extensiondev.rst | 4 ++-- docs/server.rst | 2 +- docs/shell.rst | 2 +- docs/upgrading.rst | 18 ++++++++++++++---- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 688b6811..e72c9ace 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -289,7 +289,7 @@ thing, like it does for :class:`request` and :class:`session`. It's now also possible to use the ``in`` operator on it to see if an attribute is defined and it yields all keys on iteration. - As of 1.0 you can use :meth:`pop` and :meth:`setdefault` in the same + As of 0.11 you can use :meth:`pop` and :meth:`setdefault` in the same way you would use them on a dictionary. This is a proxy. See :ref:`notes-on-proxies` for more information. diff --git a/docs/cli.rst b/docs/cli.rst index 72184b0a..5af0cf88 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -7,7 +7,7 @@ Command Line Interface .. currentmodule:: flask -One of the nice new features in Flask 1.0 is the built-in integration of +One of the nice new features in Flask 0.11 is the built-in integration of the `click `_ command line interface. This enables a wide range of new features for the Flask ecosystem and your own applications. diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index af593531..4210cae3 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -131,7 +131,7 @@ Logging to a File Even if you get mails, you probably also want to log warnings. It's a good idea to keep as much information around that might be required to -debug a problem. By default as of Flask 1.0, errors are logged to your +debug a problem. By default as of Flask 0.11, errors are logged to your webserver's log automatically. Warnings however are not. Please note that Flask itself will not issue any warnings in the core system, so it's your responsibility to warn in the code if something seems odd. diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 9119abdb..d73d6019 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -408,8 +408,8 @@ 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 1.0, most Flask extensions have transitioned to the new naming -schema. The ``flask.ext.foo`` compatibility alias is still in Flask 1.0 but is +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``. diff --git a/docs/server.rst b/docs/server.rst index 98bbfdad..f8332ebf 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -5,7 +5,7 @@ Development Server .. currentmodule:: flask -Starting with Flask 1.0 there are multiple built-in ways to run a +Starting with Flask 0.11 there are multiple built-in ways to run a development server. The best one is the :command:`flask` command line utility but you can also continue using the :meth:`Flask.run` method. diff --git a/docs/shell.rst b/docs/shell.rst index 0caf9139..9d9bb5f9 100644 --- a/docs/shell.rst +++ b/docs/shell.rst @@ -29,7 +29,7 @@ chapter of the documentation first. Command Line Interface ---------------------- -Starting with Flask 1.0 the recommended way to work with the shell is the +Starting with Flask 0.11 the recommended way to work with the shell is the ``flask shell`` command which does a lot of this automatically for you. For instance the shell is automatically initialized with a loaded application context. diff --git a/docs/upgrading.rst b/docs/upgrading.rst index eede3f6e..5ab73868 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -21,13 +21,23 @@ installation, make sure to pass it the :option:`-U` parameter:: .. _upgrading-to-10: -Version 1.0 ------------ +Version 0.11 +------------ + +0.11 is an odd release in the Flask release cycle because it was supposed +to be the 1.0 release. However because there was such a long lead time up +to the release we decided to push out a 0.11 release first with some +changes removed to make the transition easier. If you have been tracking +the master branch which was 1.0 you might see some unexpected changes. + +In case you did track the master branch you will notice that `flask --app` +is removed now. You need to use the environment variable to specify an +application. Debugging ````````` -Flask 1.0 removed the ``debug_log_format`` attribute from Flask +Flask 0.11 removed the ``debug_log_format`` attribute from Flask applications. Instead the new ``LOGGER_HANDLER_POLICY`` configuration can be used to disable the default log handlers and custom log handlers can be set up. @@ -206,7 +216,7 @@ before, you should catch them with :exc:`RuntimeError` now. Additionally the :func:`~flask.send_file` function is now issuing deprecation warnings if you depend on functionality that will be removed -in Flask 1.0. Previously it was possible to use etags and mimetypes +in Flask 0.11. Previously it was possible to use etags and mimetypes when file objects were passed. This was unreliable and caused issues for a few setups. If you get a deprecation warning, make sure to update your application to work with either filenames there or disable From 21d595bee73e65a28201709bd1a8b97c42aff5d2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 21:05:39 +0200 Subject: [PATCH 125/440] Change changelog to 0.11 --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 2befdc87..9e3b805f 100644 --- a/CHANGES +++ b/CHANGES @@ -3,8 +3,8 @@ Flask Changelog Here you can see the full list of changes between each Flask release. -Version 1.0 ------------ +Version 0.11 +------------ (release date to be announced, codename to be selected) From 9594876c1f6377a3f8b911d31e8c941295baa281 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 21:29:01 +0200 Subject: [PATCH 126/440] Added plugin support to the cli --- docs/cli.rst | 34 ++++++++++++++++++++++++++++++++++ flask/cli.py | 19 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/docs/cli.rst b/docs/cli.rst index 5af0cf88..d1b0850d 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -214,3 +214,37 @@ step. Whenever click now needs to operate on a Flask application it will call that function with the script info and ask for it to be created. 4. All is rounded up by invoking the script. + +CLI Plugins +----------- + +Flask extensions can always patch the `Flask.cli` instance with more +commands if they want. However there is a second way to add CLI plugins +to Flask which is through `setuptools`. If you make a Python package that +should export a Flask command line plugin you can ship a `setup.py` file +that declares an entrypoint that points to a click command: + +Example `setup.py`:: + + from setuptools import setup + + setup( + name='flask-my-extension', + ... + entry_points=''' + [flask.commands] + my-command=mypackage.commands:cli + ''', + ) + +Inside `mypackage/comamnds.py` you can then export a Click object:: + + import click + + @click.command() + def cli(): + """This is an example command.""" + +Once that package is installed in the same virtualenv as Flask itself you +can run ``flask my-command`` to invoke your command. This is useful to +provide extra functionality that Flask itself cannot ship. diff --git a/flask/cli.py b/flask/cli.py index 6bd9398b..b94e9317 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -282,7 +282,24 @@ class FlaskGroup(AppGroup): self.add_command(run_command) self.add_command(shell_command) + self._loaded_plugin_commands = False + + def _load_plugin_commands(self): + if self._loaded_plugin_commands: + return + try: + import pkg_resources + except ImportError: + self._loaded_plugin_commands = True + return + + for ep in pkg_resources.iter_entry_points('flask.commands'): + self.add_command(ep.load(), ep.name) + self._loaded_plugin_commands = True + def get_command(self, ctx, name): + self._load_plugin_commands() + # We load built-in commands first as these should always be the # same no matter what the app does. If the app does want to # override this it needs to make a custom instance of this group @@ -303,6 +320,8 @@ class FlaskGroup(AppGroup): pass def list_commands(self, ctx): + self._load_plugin_commands() + # The commands available is the list of both the application (if # available) plus the builtin commands. rv = set(click.Group.list_commands(self, ctx)) From 8482ce6b8ca9f1110b86bff4c3f998655fdecf8a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 21:46:56 +0200 Subject: [PATCH 127/440] Improve application context popping Exceptions during teardown handling will no longer leave application contexts lingering around. This fixes #1767 --- CHANGES | 2 ++ flask/ctx.py | 80 +++++++++++++++++++++++--------------------- tests/test_appctx.py | 22 ++++++++++++ 3 files changed, 66 insertions(+), 38 deletions(-) diff --git a/CHANGES b/CHANGES index 9e3b805f..0fa71e08 100644 --- a/CHANGES +++ b/CHANGES @@ -77,6 +77,8 @@ Version 0.11 - ``send_from_directory`` now raises BadRequest if the filename is invalid on the server OS (pull request ``#1763``). - Added the ``JSONIFY_MIMETYPE`` configuration variable (pull request ``#1728``). +- Exceptions during teardown handling will no longer leave bad application + contexts lingering around. Version 0.10.2 -------------- diff --git a/flask/ctx.py b/flask/ctx.py index 3401bd79..480d9c5c 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -181,12 +181,14 @@ class AppContext(object): def pop(self, exc=_sentinel): """Pops the app context.""" - self._refcnt -= 1 - if self._refcnt <= 0: - if exc is _sentinel: - exc = sys.exc_info()[1] - self.app.do_teardown_appcontext(exc) - rv = _app_ctx_stack.pop() + try: + self._refcnt -= 1 + if self._refcnt <= 0: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + rv = _app_ctx_stack.pop() assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ % (rv, self) appcontext_popped.send(self.app) @@ -341,38 +343,40 @@ class RequestContext(object): """ app_ctx = self._implicit_app_ctx_stack.pop() - clear_request = False - if not self._implicit_app_ctx_stack: - self.preserved = False - self._preserved_exc = None - if exc is _sentinel: - exc = sys.exc_info()[1] - self.app.do_teardown_request(exc) - - # If this interpreter supports clearing the exception information - # we do that now. This will only go into effect on Python 2.x, - # on 3.x it disappears automatically at the end of the exception - # stack. - if hasattr(sys, 'exc_clear'): - sys.exc_clear() - - request_close = getattr(self.request, 'close', None) - if request_close is not None: - request_close() - clear_request = True - - rv = _request_ctx_stack.pop() - assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ - % (rv, self) - - # get rid of circular dependencies at the end of the request - # so that we don't require the GC to be active. - if clear_request: - rv.request.environ['werkzeug.request'] = None - - # Get rid of the app as well if necessary. - if app_ctx is not None: - app_ctx.pop(exc) + try: + clear_request = False + if not self._implicit_app_ctx_stack: + self.preserved = False + self._preserved_exc = None + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + # If this interpreter supports clearing the exception information + # we do that now. This will only go into effect on Python 2.x, + # on 3.x it disappears automatically at the end of the exception + # stack. + if hasattr(sys, 'exc_clear'): + sys.exc_clear() + + request_close = getattr(self.request, 'close', None) + if request_close is not None: + request_close() + clear_request = True + finally: + rv = _request_ctx_stack.pop() + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + rv.request.environ['werkzeug.request'] = None + + # Get rid of the app as well if necessary. + if app_ctx is not None: + app_ctx.pop(exc) + + assert rv is self, 'Popped wrong request context. ' \ + '(%r instead of %r)' % (rv, self) def auto_pop(self, exc): if self.request.environ.get('flask._preserve_context') or \ diff --git a/tests/test_appctx.py b/tests/test_appctx.py index f24704ef..13b61eee 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -146,3 +146,25 @@ def test_context_refcounts(): assert res.status_code == 200 assert res.data == b'' assert called == ['request', 'app'] + + +def test_clean_pop(): + called = [] + app = flask.Flask(__name__) + + @app.teardown_request + def teardown_req(error=None): + 1 / 0 + + @app.teardown_appcontext + def teardown_app(error=None): + called.append('TEARDOWN') + + try: + with app.test_request_context(): + called.append(flask.current_app.name) + except ZeroDivisionError: + pass + + assert called == ['test_appctx', 'TEARDOWN'] + assert not flask.current_app From 3e651e795a76032eef18349a5cddaf770c0e2046 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 26 May 2016 22:57:12 +0200 Subject: [PATCH 128/440] Added sentry to docs --- docs/errorhandling.rst | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 4210cae3..9fe00cf0 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -28,6 +28,46 @@ exception to the :attr:`~flask.Flask.logger`. But there is more you can do, and we will cover some better setups to deal with errors. +Error Logging Tools +------------------- + +Sending error mails, even if just for critical ones, can become +overwhelming if enough users are hitting the error and log files are +typically never looked at. This is why we're recommending using +`Sentry `_ for dealing with application errors. +It's available as an Open Source project `on GitHub +`__ and is also available as `Hosted Version +`_ which you can try for free. Sentry +aggregates duplicate erorrs, captures the full stack trace and local +variables for debugging, and send you mails based on new errors or +frequency thresholds. + +To use Sentry you need to install the `raven` client:: + + $ pip install raven + +And then add this to your Flask app:: + + from raven.contrib.flask import Sentry + sentry = Sentry(app, dsn='YOUR_DSN_HERE') + +Of if you are using factories you can also init it later:: + + from raven.contrib.flask import Sentry + sentry = Sentry(dsn='YOUR_DSN_HERE') + + def create_app(): + app = Flask(__name__) + sentry.init_app(app) + ... + return app + +The `YOUR_DSN_HERE` value needs to be replaced with the DSN value you get +from your Sentry installation. + +Afterwards failures are automatically reported to Sentry and from there +you can receive error notifications. + .. _error-handlers: Error handlers From 42b7ab3f499d3ccecfb86220cf3107ba6231cd8f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 27 May 2016 00:17:58 +0200 Subject: [PATCH 129/440] Incorporated ThiefMaster's suggestions for docs --- docs/errorhandling.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 9fe00cf0..e2af7af4 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -33,13 +33,13 @@ Error Logging Tools Sending error mails, even if just for critical ones, can become overwhelming if enough users are hitting the error and log files are -typically never looked at. This is why we're recommending using -`Sentry `_ for dealing with application errors. -It's available as an Open Source project `on GitHub -`__ and is also available as `Hosted Version +typically never looked at. This is why we recommend using `Sentry +`_ for dealing with application errors. It's +available as an Open Source project `on GitHub +`__ and is also available as a `hosted version `_ which you can try for free. Sentry -aggregates duplicate erorrs, captures the full stack trace and local -variables for debugging, and send you mails based on new errors or +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:: From 0e06302c79c50a4b2dd43b2bcd6f909f8994ce5e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 May 2016 11:01:22 +0200 Subject: [PATCH 130/440] Release is near --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 0fa71e08..cbecd4af 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. Version 0.11 ------------ -(release date to be announced, codename to be selected) +Released on May 29th 2016, codename Absinthe. - Added support to serializing top-level arrays to :func:`flask.jsonify`. This introduces a security risk in ancient browsers. See From 1eccb62965967264acfd1a504fcc3fca0646a8be Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 May 2016 11:02:18 +0200 Subject: [PATCH 131/440] Do not bump version in setup.py --- scripts/make-release.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/make-release.py b/scripts/make-release.py index 5c16b6fa..27cbe0ee 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -80,11 +80,6 @@ def set_init_version(version): set_filename_version('flask/__init__.py', version, '__version__') -def set_setup_version(version): - info('Setting setup.py version to %s', version) - set_filename_version('setup.py', version, 'version') - - def build_and_upload(): Popen([sys.executable, 'setup.py', 'release', 'sdist', 'bdist_wheel', 'upload']).wait() @@ -140,12 +135,10 @@ def main(): fail('You have uncommitted changes in git') set_init_version(version) - set_setup_version(version) make_git_commit('Bump version number to %s', version) make_git_tag(version) build_and_upload() set_init_version(dev_version) - set_setup_version(dev_version) if __name__ == '__main__': From 13e6a01ac86f9b8c0cad692d5e5e8d600674fb6d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 May 2016 11:02:23 +0200 Subject: [PATCH 132/440] Bump version number to 0.11 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 7fd7a253..3b7d0945 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.11.dev0' +__version__ = '0.11' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From b23cd61e04b01e83defcdb9dafe8b6bcafdffc40 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 May 2016 11:02:48 +0200 Subject: [PATCH 133/440] This is 0.12-dev --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 3b7d0945..f3d5a090 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.11' +__version__ = '0.12-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From f91aea2aa0c83964cebdd71350318932baffe700 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 29 May 2016 15:46:48 +0200 Subject: [PATCH 134/440] quickstart: Remove reference to `python hello.py` Fix #1826 --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 0ebaf06c..0d0028e2 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -37,9 +37,9 @@ So what did that code do? particular function, and returns the message we want to display in the user's browser. -Just save it as :file:`hello.py` (or something similar) and run it with your Python -interpreter. Make sure to not call your application :file:`flask.py` because this -would conflict with Flask itself. +Just save it as :file:`hello.py` or something similar. Make sure to not call +your application :file:`flask.py` because this would conflict with Flask +itself. To run the application you can either use the :command:`flask` command or python's :option:`-m` switch with Flask. Before you can do that you need From 70de011d5102bea6b97010643cf36698f94f97fb Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Sun, 29 May 2016 14:49:38 +0100 Subject: [PATCH 135/440] Convert readthedocs link for their .org -> .io migration for hosted projects (#1827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per their email ‘Changes to project subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- CONTRIBUTING.rst | 2 +- docs/conf.py | 2 +- docs/deploying/wsgi-standalone.rst | 4 ++-- docs/patterns/wtforms.rst | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d72428e4..67eb3061 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -36,7 +36,7 @@ Running the testsuite --------------------- You probably want to set up a `virtualenv -`_. +`_. The minimal requirement for running the testsuite is ``py.test``. You can install it with:: diff --git a/docs/conf.py b/docs/conf.py index 880e9122..2f449da2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -250,7 +250,7 @@ intersphinx_mapping = { 'http://click.pocoo.org/': None, 'http://jinja.pocoo.org/docs/': None, 'http://www.sqlalchemy.org/docs/': None, - 'https://wtforms.readthedocs.org/en/latest/': None, + 'https://wtforms.readthedocs.io/en/latest/': None, 'https://pythonhosted.org/blinker/': None } diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index d546fcd7..ad43c144 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -25,7 +25,7 @@ For example, to run a Flask application with 4 worker processes (``-w .. _Gunicorn: http://gunicorn.org/ .. _eventlet: http://eventlet.net/ -.. _greenlet: http://greenlet.readthedocs.org/en/latest/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ Gevent ------- @@ -41,7 +41,7 @@ event loop:: http_server.serve_forever() .. _Gevent: http://www.gevent.org/ -.. _greenlet: http://greenlet.readthedocs.org/en/latest/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ .. _libev: http://software.schmorp.de/pkg/libev.html Twisted Web diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 5a84fce8..6c08b808 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -122,5 +122,5 @@ takes advantage of the :file:`_formhelpers.html` template: For more information about WTForms, head over to the `WTForms website`_. -.. _WTForms: http://wtforms.readthedocs.org/ -.. _WTForms website: http://wtforms.readthedocs.org/ +.. _WTForms: https://wtforms.readthedocs.io/ +.. _WTForms website: https://wtforms.readthedocs.io/ From ba07f5bd81f66a40333cc619d0a8a7b3c17ec49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionu=C8=9B=20Ar=C8=9B=C4=83ri=C8=99i?= Date: Sun, 29 May 2016 22:51:05 +0100 Subject: [PATCH 136/440] Show line which caused the DeprecationWarning (#1831) When raising a DeprecationWarning, show the line in the application code which caused the warning, rather than the line in Flask e.g. a file `app.py` with: ```python from flask import Flask from flask.ext.babel import Babel ``` will show: ``` app.py:2: ExtDeprecationWarning: Importing flask.ext.babel is deprecated, use flask_babel instead. ``` instead of: ``` /home/mapleoin/venv/local/lib/python2.7/site-packages/flask/exthook.py:71: ExtDeprecationWarning: Importing flask.ext.babel is deprecated, use flask_babel instead. .format(x=modname), ExtDeprecationWarning ``` --- flask/exthook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/exthook.py b/flask/exthook.py index 6522e063..d8842802 100644 --- a/flask/exthook.py +++ b/flask/exthook.py @@ -68,7 +68,7 @@ class ExtensionImporter(object): warnings.warn( "Importing flask.ext.{x} is deprecated, use flask_{x} instead." - .format(x=modname), ExtDeprecationWarning + .format(x=modname), ExtDeprecationWarning, stacklevel=2 ) for path in self.module_choices: From 9c469698904e41a9e006ff7b96e48557a4f3c418 Mon Sep 17 00:00:00 2001 From: Jochen Kupperschmidt Date: Mon, 30 May 2016 23:20:23 +0200 Subject: [PATCH 137/440] Fixed link in changelog to documentation. (#1833) Using the officially documented, shortcut package path of the `Config` class instead of the actual one. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index cbecd4af..a383ab7f 100644 --- a/CHANGES +++ b/CHANGES @@ -26,7 +26,7 @@ Released on May 29th 2016, codename Absinthe. from a view function. - Added :meth:`flask.Config.from_json`. - Added :attr:`flask.Flask.config_class`. -- Added :meth:`flask.config.Config.get_namespace`. +- Added :meth:`flask.Config.get_namespace`. - Templates are no longer automatically reloaded outside of debug mode. This can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config key. - Added a workaround for a limitation in Python 3.3's namespace loader. From a72583652329da810fc2a04e8541a0e2efd47ee1 Mon Sep 17 00:00:00 2001 From: Yoav Ram Date: Tue, 31 May 2016 00:20:35 +0300 Subject: [PATCH 138/440] Update help to > set FLASK_APP=hello.py (#1830) When running `flask --help`, the printed string contains this: > Example usage: > > set FLASK_APP=hello > set FLASK_DEBUG=1 > flask run but it actually only works with `set FLASK_APP=hello.py` so the help should be changed. This is true on my Windows 7 Python 3.5 flask 0.11 setup. --- flask/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index b94e9317..cf2c5c0c 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -449,7 +449,7 @@ The most useful commands are the "run" and "shell" command. Example usage: \b - %(prefix)s%(cmd)s FLASK_APP=hello + %(prefix)s%(cmd)s FLASK_APP=hello.py %(prefix)s%(cmd)s FLASK_DEBUG=1 %(prefix)sflask run """ % { From 83ae787f97c38bb73c9d627326710a997dbac2a8 Mon Sep 17 00:00:00 2001 From: Jochen Kupperschmidt Date: Tue, 31 May 2016 16:24:11 +0200 Subject: [PATCH 139/440] Fix typo in cli docs --- docs/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index d1b0850d..42596daf 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -237,7 +237,7 @@ Example `setup.py`:: ''', ) -Inside `mypackage/comamnds.py` you can then export a Click object:: +Inside `mypackage/commands.py` you can then export a Click object:: import click From e4c712ffd2682f963906e1d0d27e67b7f83d95ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 31 May 2016 21:20:22 +0200 Subject: [PATCH 140/440] a few more python3-compatible print (#1840) --- scripts/flask-07-upgrade.py | 3 ++- scripts/make-release.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/flask-07-upgrade.py b/scripts/flask-07-upgrade.py index 7cab94e1..7fbdd49c 100644 --- a/scripts/flask-07-upgrade.py +++ b/scripts/flask-07-upgrade.py @@ -19,6 +19,7 @@ :copyright: (c) Copyright 2015 by Armin Ronacher. :license: see LICENSE for more details. """ +from __future__ import print_function import re import os import inspect @@ -59,7 +60,7 @@ def make_diff(filename, old, new): posixpath.normpath(posixpath.join('a', filename)), posixpath.normpath(posixpath.join('b', filename)), lineterm=''): - print line + print(line) def looks_like_teardown_function(node): diff --git a/scripts/make-release.py b/scripts/make-release.py index 27cbe0ee..fc6421ab 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -10,6 +10,7 @@ :copyright: (c) 2015 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from __future__ import print_function import sys import os import re @@ -85,12 +86,12 @@ def build_and_upload(): def fail(message, *args): - print >> sys.stderr, 'Error:', message % args + print('Error:', message % args, file=sys.stderr) sys.exit(1) def info(message, *args): - print >> sys.stderr, message % args + print(message % args, file=sys.stderr) def get_git_tags(): From fd1a355899691215bda059f410256e6fff470955 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 2 Jun 2016 09:44:41 +0200 Subject: [PATCH 141/440] Added test-requirements.txt. Refs #1835 --- Makefile | 1 + test-requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 test-requirements.txt diff --git a/Makefile b/Makefile index 350aa9a4..9bcdebc2 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ all: clean-pyc test test: + pip install -r test-requirements.txt -q FLASK_DEBUG= py.test tests examples tox-test: diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..e079f8a6 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +pytest From 6bee3e4995066466a35ac080844d4962a728d084 Mon Sep 17 00:00:00 2001 From: RamiC Date: Thu, 2 Jun 2016 13:35:16 +0300 Subject: [PATCH 142/440] Add a --version switch to flask cli re #1828 --- flask/cli.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index cf2c5c0c..d1e983a8 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -18,7 +18,7 @@ import click from ._compat import iteritems, reraise from .helpers import get_debug_flag - +from . import __version__ class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" @@ -108,6 +108,22 @@ def find_default_import_path(): return app +def get_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + message = 'Flask %(version)s\nPython %(python_version)s' + click.echo(message % { + 'version': __version__, + 'python_version': sys.version[:3], + }, color=ctx.color) + ctx.exit() + +version_option = click.Option(['--version'], + help='Show the flask version', + expose_value=False, + callback=get_version, + is_flag=True, is_eager=True) + class DispatchingApp(object): """Special application that dispatches to a flask application which is imported by name in a background thread. If an error happens @@ -270,12 +286,19 @@ class FlaskGroup(AppGroup): :param add_default_commands: if this is True then the default run and shell commands wil be added. + :param add_version_option: adds the :option:`--version` option. :param create_app: an optional callback that is passed the script info and returns the loaded app. """ - def __init__(self, add_default_commands=True, create_app=None, **extra): - AppGroup.__init__(self, **extra) + def __init__(self, add_default_commands=True, create_app=None, + add_version_option=True, **extra): + params = list(extra.pop('params', None) or ()) + + if add_version_option: + params.append(version_option) + + AppGroup.__init__(self, params=params, **extra) self.create_app = create_app if add_default_commands: From 6b28ceba83c3b76eea2e5a89327318af67404298 Mon Sep 17 00:00:00 2001 From: RamiC Date: Thu, 2 Jun 2016 13:55:00 +0300 Subject: [PATCH 143/440] Use the whole sys.version string --- flask/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index d1e983a8..90eb0353 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -114,7 +114,7 @@ def get_version(ctx, param, value): message = 'Flask %(version)s\nPython %(python_version)s' click.echo(message % { 'version': __version__, - 'python_version': sys.version[:3], + 'python_version': sys.version, }, color=ctx.color) ctx.exit() From 41f3d67dffe0263d46e7829819393c1abcf3d0f1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 2 Jun 2016 13:53:35 +0200 Subject: [PATCH 144/440] Update CHANGES --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index a383ab7f..a6dd5300 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.12 +------------ + +- the cli command now responds to `--version`. + Version 0.11 ------------ From 390cd5e4eec8340d74e8b72b2d51a5a3eeef3842 Mon Sep 17 00:00:00 2001 From: James Farrington Date: Thu, 2 Jun 2016 11:58:02 -0700 Subject: [PATCH 145/440] Fixed #1846 --- tests/test_helpers.py | 8 ++++++++ tox.ini | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 6dc41fad..2338844e 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -19,6 +19,10 @@ from werkzeug.exceptions import BadRequest from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date from flask._compat import StringIO, text_type +try: + import simplejson +except ImportError: + import json as simplejson def has_encoding(name): @@ -114,6 +118,10 @@ class TestJSON(object): assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data) == d + def test_simplejson_does_not_escape_slashes(self): + """Test that \\/ is no longer standard behavior.""" + assert '\\/' not in simplejson.dumps('/') + def test_jsonify_dicts(self): """Test jsonify with dicts and kwargs unpacking.""" d = dict( diff --git a/tox.ini b/tox.ini index bd936a4b..5e879cec 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py26,py27,pypy}-{lowest,release,devel}, {py33,py34,py35}-{release,devel} +envlist = {py26,py27,pypy}-{lowest,release,devel,simplejson}, {py33,py34,py35}-{release,devel,simplejson} [testenv] commands = @@ -19,6 +19,7 @@ deps= devel: git+https://github.com/pallets/jinja.git devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git + simplejson: simplejson [testenv:docs] deps = sphinx From d9a98dd536b4cac42fc9012c6184251a5ae2ad7a Mon Sep 17 00:00:00 2001 From: Prachi Shirish Khadke Date: Thu, 2 Jun 2016 11:26:28 -0700 Subject: [PATCH 146/440] Document flash message size limit Reason: Messages of size 68,493 - 91,326 characters cause flash to fail silently. Session cookies cannot have such large messages. Issue: pallets/flask#1789 --- docs/patterns/flashing.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index b2de07ce..dc20a08c 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -9,7 +9,9 @@ application. Flask provides a really simple way to give feedback to a user with the flashing system. The flashing system basically makes it possible to record a message at the end of a request and access it next request and only next request. This is usually combined with a layout -template that does this. +template that does this. Note that browsers and sometimes web servers enforce +a limit on cookie sizes. This means that flashing messages that are too +large for session cookies causes message flashing to fail silently. Simple Flashing --------------- From 14e4207397adeb09a14831bd01c21508c9ef4840 Mon Sep 17 00:00:00 2001 From: Hyunchel Kim Date: Thu, 2 Jun 2016 10:29:33 -0700 Subject: [PATCH 147/440] Add issue template, resolves #1773 On issue #1773, we wanted use issue templates to accomplish two things: 1. guide questions into StackOverflow and IRC channel 2. not duplicate CONTRIBUTING content To resolve these, ISSUE_TEMPLATE is added to remind users not to ask questions. --- .github/ISSUE_TEMPLATE.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.rst diff --git a/.github/ISSUE_TEMPLATE.rst b/.github/ISSUE_TEMPLATE.rst new file mode 100644 index 00000000..edd0a5c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.rst @@ -0,0 +1,2 @@ +The issue tracker is a tool to address bugs. +Please use `#pocoo` IRC channel on freenode or `StackOverflow` for questions. From 7e8d7d43c8852f1567a93838ae7983bf519ddcb1 Mon Sep 17 00:00:00 2001 From: Emily Manders Date: Thu, 2 Jun 2016 12:47:36 -0700 Subject: [PATCH 148/440] Added link to deploying documentation --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 6cbc4306..983f7611 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,8 @@ And run it: $ python hello.py * Running on http://localhost:5000/ + Ready for production? `Read this first `. + Links ````` From cbdd2aa141fc7c0f82b3667bbc55d50843d6627c Mon Sep 17 00:00:00 2001 From: Ryan Backman Date: Thu, 2 Jun 2016 14:05:49 -0700 Subject: [PATCH 149/440] Document Runtime Error when running outside of application context --- docs/appcontext.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index 672b6bfd..baa71315 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -74,6 +74,11 @@ The application context is also used by the :func:`~flask.url_for` function in case a ``SERVER_NAME`` was configured. This allows you to generate URLs even in the absence of a request. +If a request context has not been pushed and an application context has +not been explicitly set, a ``RuntimeError`` will be raised. +:: + RuntimeError: Working outside of application context. + Locality of the Context ----------------------- From 63b5dab0fc889f490b9fee803ae67efa3df28a09 Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Thu, 2 Jun 2016 14:14:56 -0700 Subject: [PATCH 150/440] Add subclassing pattern/example to fix issue #221. --- docs/becomingbig.rst | 2 +- docs/patterns/index.rst | 1 + docs/patterns/subclassing.rst | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 docs/patterns/subclassing.rst diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst index 8b0a2743..df470a76 100644 --- a/docs/becomingbig.rst +++ b/docs/becomingbig.rst @@ -35,7 +35,7 @@ Subclass. The :class:`~flask.Flask` class has many methods designed for subclassing. You can quickly add or customize behavior by subclassing :class:`~flask.Flask` (see the linked method docs) and using that subclass wherever you instantiate an -application class. This works well with :ref:`app-factories`. +application class. This works well with :ref:`app-factories`. See :doc:`/patterns/subclassing` for an example. Wrap with middleware. --------------------- diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index a64b0bb2..78a66a1d 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -41,3 +41,4 @@ Snippet Archives `_. methodoverrides requestchecksum celery + subclassing diff --git a/docs/patterns/subclassing.rst b/docs/patterns/subclassing.rst new file mode 100644 index 00000000..ebef57bd --- /dev/null +++ b/docs/patterns/subclassing.rst @@ -0,0 +1,22 @@ +Subclassing Flask +================= + +The :class:`~flask.Flask` class is designed for subclassing. + +One reason to subclass would be customizing the Jinja2 :class:`~jinja2.Environment`. For example, to add a new global template variable:: + + from flask import Flask + from datetime import datetime + + class MyFlask(Flask): + """ Flask with more global template vars """ + + def create_jinja_environment(self): + """ Initialize my custom Jinja environment. """ + jinja_env = super(MyFlask, self).create_jinja_environment(self) + jinja_env.globals.update( + current_time = datetime.datetime.now() + ) + return jinja_env + +This is the recommended approach for overriding or augmenting Flask's internal functionality. From db299c02a1e066469a542bd3fca64e7c237a6591 Mon Sep 17 00:00:00 2001 From: Adrian Date: Thu, 2 Jun 2016 23:27:41 +0200 Subject: [PATCH 151/440] Improve GitHub issue template --- .github/ISSUE_TEMPLATE.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.rst b/.github/ISSUE_TEMPLATE.rst index edd0a5c9..8854961a 100644 --- a/.github/ISSUE_TEMPLATE.rst +++ b/.github/ISSUE_TEMPLATE.rst @@ -1,2 +1,2 @@ The issue tracker is a tool to address bugs. -Please use `#pocoo` IRC channel on freenode or `StackOverflow` for questions. +Please use the #pocoo IRC channel on freenode or Stack Overflow for questions. From 024fbe5a6076d0f3f793cd4a66d0b50decdb2a1a Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 2 Jun 2016 14:54:49 -0700 Subject: [PATCH 152/440] Revert "Adds simplejson as a testing target." (#1865) --- tests/test_helpers.py | 8 -------- tox.ini | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 2338844e..6dc41fad 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -19,10 +19,6 @@ from werkzeug.exceptions import BadRequest from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date from flask._compat import StringIO, text_type -try: - import simplejson -except ImportError: - import json as simplejson def has_encoding(name): @@ -118,10 +114,6 @@ class TestJSON(object): assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data) == d - def test_simplejson_does_not_escape_slashes(self): - """Test that \\/ is no longer standard behavior.""" - assert '\\/' not in simplejson.dumps('/') - def test_jsonify_dicts(self): """Test jsonify with dicts and kwargs unpacking.""" d = dict( diff --git a/tox.ini b/tox.ini index 5e879cec..bd936a4b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py26,py27,pypy}-{lowest,release,devel,simplejson}, {py33,py34,py35}-{release,devel,simplejson} +envlist = {py26,py27,pypy}-{lowest,release,devel}, {py33,py34,py35}-{release,devel} [testenv] commands = @@ -19,7 +19,6 @@ deps= devel: git+https://github.com/pallets/jinja.git devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git - simplejson: simplejson [testenv:docs] deps = sphinx From 447f591d2b5507bbd25ea9f1aa0a33522b444822 Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Thu, 2 Jun 2016 15:05:14 -0700 Subject: [PATCH 153/440] Rewrite subclassing example with a better use-case. --- docs/patterns/subclassing.rst | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/patterns/subclassing.rst b/docs/patterns/subclassing.rst index ebef57bd..d8de2335 100644 --- a/docs/patterns/subclassing.rst +++ b/docs/patterns/subclassing.rst @@ -3,20 +3,15 @@ Subclassing Flask The :class:`~flask.Flask` class is designed for subclassing. -One reason to subclass would be customizing the Jinja2 :class:`~jinja2.Environment`. For example, to add a new global template variable:: - - from flask import Flask - from datetime import datetime +For example, you may want to override how request parameters are handled to preserve their order:: + from flask import Flask, Request + from werkzeug.datastructures import ImmutableOrderedMultiDict + class MyRequest(Request): + """Request subclass to override request parameter storage""" + parameter_storage_class = ImmutableOrderedMultiDict class MyFlask(Flask): - """ Flask with more global template vars """ - - def create_jinja_environment(self): - """ Initialize my custom Jinja environment. """ - jinja_env = super(MyFlask, self).create_jinja_environment(self) - jinja_env.globals.update( - current_time = datetime.datetime.now() - ) - return jinja_env + """Flask subclass using the custom request class""" + request_class = MyRequest This is the recommended approach for overriding or augmenting Flask's internal functionality. From d88c08e56f7398206596129036b8f101be11cba0 Mon Sep 17 00:00:00 2001 From: Jason Brazeal Date: Thu, 2 Jun 2016 15:40:59 -0700 Subject: [PATCH 154/440] improved documentation for config.from_object (#1870) --- docs/config.rst | 1 + flask/config.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 3039b3ea..4958d471 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -310,6 +310,7 @@ that experience: limit yourself to request-only accesses to the configuration you can reconfigure the object later on as needed. +.. _config-dev-prod: Development / Production ------------------------ diff --git a/flask/config.py b/flask/config.py index 426a23a2..36e8a123 100644 --- a/flask/config.py +++ b/flask/config.py @@ -143,10 +143,12 @@ class Config(dict): - a string: in this case the object with that name will be imported - an actual object reference: that object is used directly - Objects are usually either modules or classes. + Objects are usually either modules or classes. :meth:`from_object` + loads only the uppercase attributes of the module/class. A ``dict`` + object will not work with :meth:`from_object` because the keys of a + ``dict`` are not attributes of the ``dict`` class. - Just the uppercase variables in that object are stored in the config. - Example usage:: + Example of module-based configuration:: app.config.from_object('yourapplication.default_config') from yourapplication import default_config @@ -157,6 +159,9 @@ class Config(dict): with :meth:`from_pyfile` and ideally from a location not within the package because the package might be installed system wide. + See :ref:`config-dev-prod` for an example of class-based configuration + using :meth:`from_object`. + :param obj: an import name or object """ if isinstance(obj, string_types): From 047efac537abad9e3880f545d8b767f6e6be3786 Mon Sep 17 00:00:00 2001 From: jphilipsen05 Date: Thu, 2 Jun 2016 17:56:08 -0700 Subject: [PATCH 155/440] Coverage for test_static_path_deprecated and test_static_url_path (#1860) --- tests/test_basic.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index 8c5b0def..45cad691 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1116,6 +1116,25 @@ def test_static_files(): rv.close() +def test_static_path_deprecated(): + with pytest.deprecated_call(): + app = flask.Flask(__name__, static_path='/foo') + app.testing = True + rv = app.test_client().get('/foo/index.html') + assert rv.status_code == 200 + with app.test_request_context(): + assert flask.url_for('static', filename='index.html') == '/foo/index.html' + + +def test_static_url_path(): + app = flask.Flask(__name__, static_url_path='/foo') + app.testing = True + rv = app.test_client().get('/foo/index.html') + assert rv.status_code == 200 + with app.test_request_context(): + assert flask.url_for('static', filename='index.html') == '/foo/index.html' + + def test_none_response(): app = flask.Flask(__name__) app.testing = True From 62aaee02f754c74e0a051097c869cdabb0f0a09d Mon Sep 17 00:00:00 2001 From: Hyunchel Kim Date: Thu, 2 Jun 2016 22:15:00 -0700 Subject: [PATCH 156/440] Add a link to Extension Development (#1875) --- docs/extensions.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/extensions.rst b/docs/extensions.rst index d1d24807..6deb9652 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -25,6 +25,13 @@ importable from ``flask_foo``:: import flask_foo +Building Extensions +------------------- + +While `Flask Extension Registry`_ contains many Flask extensions, you may not find +an extension that fits your need. If this is the case, you can always create your own. +Consider reading :ref:`extension-dev` to develop your own Flask extension. + Flask Before 0.8 ---------------- From fa327fd4fadcca746b466dfd8dbd166a50d8efad Mon Sep 17 00:00:00 2001 From: wldtyp Date: Fri, 3 Jun 2016 01:00:55 -0700 Subject: [PATCH 157/440] Tutorial: Note extensions for encrypting passwords (#1854) Fix #836 --- docs/tutorial/views.rst | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 618c97c6..bdfdf2f0 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -94,11 +94,24 @@ if the user was logged in. session.pop('logged_in', None) flash('You were logged out') return redirect(url_for('show_entries')) - -Note that it is not a good idea to store passwords in plain text. You want to -protect login credentials if someone happens to have access to your database. -One way to do this is to use Security Helpers from Werkzeug to hash the -password. However, the emphasis of this tutorial is to demonstrate the basics -of Flask and plain text passwords are used for simplicity. + +.. admonition:: Security Note + + Passwords should never be stored in plain text in a production + system. This tutorial uses plain text passwords for simplicity. If you + plan to release a project based off this tutorial out into the world, + passwords should be both `hashed and salted`_ before being stored in a + database or file. + + Fortunately, there are Flask extensions for the purpose of + hashing passwords and verifying passwords against hashes, so adding + this functionality is fairly straight forward. There are also + many general python libraries that can be used for hashing. + + You can find a list of recommended Flask extensions + `here `_ + Continue with :ref:`tutorial-templates`. + +.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/ \ No newline at end of file From d393597c507ea62df534ba2ffb8a4e77cf3f1548 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 3 Jun 2016 13:56:42 +0200 Subject: [PATCH 158/440] Use recwarn everywhere ...instead of custom fixture. Also assert that no warnings are left over after the test. --- tests/conftest.py | 9 +-- tests/test_basic.py | 7 +- tests/test_deprecations.py | 20 ++--- tests/test_ext.py | 12 +++ tests/test_helpers.py | 158 ++++++++++++++++++++----------------- 5 files changed, 112 insertions(+), 94 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7a209c22..cea73092 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -126,8 +126,7 @@ def purge_module(request): return inner -@pytest.fixture -def catch_deprecation_warnings(): - import warnings - warnings.simplefilter('default', category=DeprecationWarning) - return lambda: warnings.catch_warnings(record=True) +@pytest.yield_fixture(autouse=True) +def catch_deprecation_warnings(recwarn): + yield + assert not recwarn.list diff --git a/tests/test_basic.py b/tests/test_basic.py index 45cad691..57d8e729 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1116,9 +1116,10 @@ def test_static_files(): rv.close() -def test_static_path_deprecated(): - with pytest.deprecated_call(): - app = flask.Flask(__name__, static_path='/foo') +def test_static_path_deprecated(recwarn): + app = flask.Flask(__name__, static_path='/foo') + recwarn.pop(DeprecationWarning) + app.testing = True rv = app.test_client().get('/foo/index.html') assert rv.status_code == 200 diff --git a/tests/test_deprecations.py b/tests/test_deprecations.py index 757aacd0..666f7d56 100644 --- a/tests/test_deprecations.py +++ b/tests/test_deprecations.py @@ -16,7 +16,7 @@ import flask class TestRequestDeprecation(object): - def test_request_json(self, catch_deprecation_warnings): + def test_request_json(self, recwarn): """Request.json is deprecated""" app = flask.Flask(__name__) app.testing = True @@ -27,13 +27,11 @@ class TestRequestDeprecation(object): print(flask.request.json) return 'OK' - with catch_deprecation_warnings() as captured: - c = app.test_client() - c.post('/', data='{"spam": 42}', content_type='application/json') + c = app.test_client() + c.post('/', data='{"spam": 42}', content_type='application/json') + recwarn.pop(DeprecationWarning) - assert len(captured) == 1 - - def test_request_module(self, catch_deprecation_warnings): + def test_request_module(self, recwarn): """Request.module is deprecated""" app = flask.Flask(__name__) app.testing = True @@ -43,8 +41,6 @@ class TestRequestDeprecation(object): assert flask.request.module is None return 'OK' - with catch_deprecation_warnings() as captured: - c = app.test_client() - c.get('/') - - assert len(captured) == 1 + c = app.test_client() + c.get('/') + recwarn.pop(DeprecationWarning) diff --git a/tests/test_ext.py b/tests/test_ext.py index f5728312..d336e404 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -20,6 +20,18 @@ except ImportError: from flask._compat import PY2 +@pytest.fixture(autouse=True) +def disable_extwarnings(request, recwarn): + from flask.exthook import ExtDeprecationWarning + + def inner(): + assert set(w.category for w in recwarn.list) \ + <= set([ExtDeprecationWarning]) + recwarn.clear() + + request.addfinalizer(inner) + + @pytest.fixture(autouse=True) def importhook_setup(monkeypatch, request): # we clear this out for various reasons. The most important one is diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 6dc41fad..c7dde5c7 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -337,7 +337,7 @@ class TestSendfile(object): assert rv.data == f.read() rv.close() - def test_send_file_xsendfile(self): + def test_send_file_xsendfile(self, catch_deprecation_warnings): app = flask.Flask(__name__) app.use_x_sendfile = True with app.test_request_context(): @@ -349,90 +349,100 @@ class TestSendfile(object): assert rv.mimetype == 'text/html' rv.close() - def test_send_file_object(self, catch_deprecation_warnings): + def test_send_file_object(self, recwarn): app = flask.Flask(__name__) - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb') - rv = flask.send_file(f) - rv.direct_passthrough = False - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' - rv.close() - # mimetypes + etag - assert len(captured) == 2 + + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb') + rv = flask.send_file(f) + rv.direct_passthrough = False + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + assert rv.mimetype == 'text/html' + rv.close() + + # mimetypes + etag + assert len(recwarn.list) == 2 + recwarn.clear() app.use_x_sendfile = True - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - rv.close() - # mimetypes + etag - assert len(captured) == 2 + + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f) + assert rv.mimetype == 'text/html' + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + rv.close() + + # mimetypes + etag + recwarn.pop() + recwarn.pop() app.use_x_sendfile = False with app.test_request_context(): - with catch_deprecation_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f) - rv.direct_passthrough = False - assert rv.data == b'Test' - assert rv.mimetype == 'application/octet-stream' - rv.close() + f = StringIO('Test') + rv = flask.send_file(f) + rv.direct_passthrough = False + assert rv.data == b'Test' + assert rv.mimetype == 'application/octet-stream' + rv.close() + # etags - assert len(captured) == 1 - with catch_deprecation_warnings() as captured: - class PyStringIO(object): - def __init__(self, *args, **kwargs): - self._io = StringIO(*args, **kwargs) - def __getattr__(self, name): - return getattr(self._io, name) - f = PyStringIO('Test') - f.name = 'test.txt' - rv = flask.send_file(f) - rv.direct_passthrough = False - assert rv.data == b'Test' - assert rv.mimetype == 'text/plain' - rv.close() + recwarn.pop() + + class PyStringIO(object): + def __init__(self, *args, **kwargs): + self._io = StringIO(*args, **kwargs) + def __getattr__(self, name): + return getattr(self._io, name) + f = PyStringIO('Test') + f.name = 'test.txt' + rv = flask.send_file(f) + rv.direct_passthrough = False + assert rv.data == b'Test' + assert rv.mimetype == 'text/plain' + rv.close() + # attachment_filename and etags - assert len(captured) == 3 - with catch_deprecation_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f, mimetype='text/plain') - rv.direct_passthrough = False - assert rv.data == b'Test' - assert rv.mimetype == 'text/plain' - rv.close() + recwarn.pop() + recwarn.pop() + recwarn.pop() + + f = StringIO('Test') + rv = flask.send_file(f, mimetype='text/plain') + rv.direct_passthrough = False + assert rv.data == b'Test' + assert rv.mimetype == 'text/plain' + rv.close() + # etags - assert len(captured) == 1 + recwarn.pop() app.use_x_sendfile = True - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = StringIO('Test') - rv = flask.send_file(f) - assert 'x-sendfile' not in rv.headers - rv.close() - # etags - assert len(captured) == 1 - - def test_attachment(self, catch_deprecation_warnings): - app = flask.Flask(__name__) - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f, as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - rv.close() - # mimetypes + etag - assert len(captured) == 2 + + with app.test_request_context(): + f = StringIO('Test') + rv = flask.send_file(f) + assert 'x-sendfile' not in rv.headers + rv.close() + + # etags + recwarn.pop() + + def test_attachment(self, recwarn): + app = flask.Flask(__name__) + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f, as_attachment=True) + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + rv.close() + + # mimetypes + etag + assert len(recwarn.list) == 2 + recwarn.clear() with app.test_request_context(): assert options['filename'] == 'index.html' From 293eb583f62e930c84dc1612fadf60185c0a9174 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 3 Jun 2016 14:04:25 +0200 Subject: [PATCH 159/440] More explicit warning categories --- tests/test_helpers.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index c7dde5c7..ac18d26c 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -362,8 +362,8 @@ class TestSendfile(object): rv.close() # mimetypes + etag - assert len(recwarn.list) == 2 - recwarn.clear() + recwarn.pop(DeprecationWarning) + recwarn.pop(DeprecationWarning) app.use_x_sendfile = True @@ -377,8 +377,8 @@ class TestSendfile(object): rv.close() # mimetypes + etag - recwarn.pop() - recwarn.pop() + recwarn.pop(DeprecationWarning) + recwarn.pop(DeprecationWarning) app.use_x_sendfile = False with app.test_request_context(): @@ -390,7 +390,7 @@ class TestSendfile(object): rv.close() # etags - recwarn.pop() + recwarn.pop(DeprecationWarning) class PyStringIO(object): def __init__(self, *args, **kwargs): @@ -406,9 +406,9 @@ class TestSendfile(object): rv.close() # attachment_filename and etags - recwarn.pop() - recwarn.pop() - recwarn.pop() + a = recwarn.pop(DeprecationWarning) + b = recwarn.pop(DeprecationWarning) + c = recwarn.pop(UserWarning) # file not found f = StringIO('Test') rv = flask.send_file(f, mimetype='text/plain') @@ -418,7 +418,7 @@ class TestSendfile(object): rv.close() # etags - recwarn.pop() + recwarn.pop(DeprecationWarning) app.use_x_sendfile = True @@ -429,7 +429,7 @@ class TestSendfile(object): rv.close() # etags - recwarn.pop() + recwarn.pop(DeprecationWarning) def test_attachment(self, recwarn): app = flask.Flask(__name__) From 6c359e0f532d18a71895f035dd1326ca5d2d67f8 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 3 Jun 2016 14:19:25 +0200 Subject: [PATCH 160/440] Eliminate some resource warnings --- tests/conftest.py | 2 ++ tests/test_basic.py | 4 ++++ tests/test_helpers.py | 39 ++++++++++++++++++++------------------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cea73092..8c9541de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ :license: BSD, see LICENSE for more details. """ import flask +import gc import os import sys import pkgutil @@ -129,4 +130,5 @@ def purge_module(request): @pytest.yield_fixture(autouse=True) def catch_deprecation_warnings(recwarn): yield + gc.collect() assert not recwarn.list diff --git a/tests/test_basic.py b/tests/test_basic.py index 57d8e729..95417c35 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1123,6 +1123,8 @@ def test_static_path_deprecated(recwarn): app.testing = True rv = app.test_client().get('/foo/index.html') assert rv.status_code == 200 + rv.close() + with app.test_request_context(): assert flask.url_for('static', filename='index.html') == '/foo/index.html' @@ -1132,6 +1134,8 @@ def test_static_url_path(): app.testing = True rv = app.test_client().get('/foo/index.html') assert rv.status_code == 200 + rv.close() + with app.test_request_context(): assert flask.url_for('static', filename='index.html') == '/foo/index.html' diff --git a/tests/test_helpers.py b/tests/test_helpers.py index ac18d26c..1fec1d87 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -353,13 +353,13 @@ class TestSendfile(object): app = flask.Flask(__name__) with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb') - rv = flask.send_file(f) - rv.direct_passthrough = False - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' - rv.close() + with open(os.path.join(app.root_path, 'static/index.html'), mode='rb') as f: + rv = flask.send_file(f) + rv.direct_passthrough = False + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + assert rv.mimetype == 'text/html' + rv.close() # mimetypes + etag recwarn.pop(DeprecationWarning) @@ -368,13 +368,13 @@ class TestSendfile(object): app.use_x_sendfile = True with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - rv.close() + with open(os.path.join(app.root_path, 'static/index.html')) as f: + rv = flask.send_file(f) + assert rv.mimetype == 'text/html' + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + rv.close() # mimetypes + etag recwarn.pop(DeprecationWarning) @@ -434,11 +434,12 @@ class TestSendfile(object): def test_attachment(self, recwarn): app = flask.Flask(__name__) with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f, as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - 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) + value, options = \ + parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + rv.close() # mimetypes + etag assert len(recwarn.list) == 2 From 8458cc5cd10bac1dabea3dae86424bce46926a49 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Thu, 2 Jun 2016 10:04:48 -0700 Subject: [PATCH 161/440] Remove deprecation warnings for add_etags & mimetype guessing for send_file() Fix #1849 --- AUTHORS | 1 + CHANGES | 2 ++ flask/helpers.py | 31 +++++++++---------------------- tests/test_helpers.py | 30 ++---------------------------- 4 files changed, 14 insertions(+), 50 deletions(-) diff --git a/AUTHORS b/AUTHORS index d081d9f8..cc157dc4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Patches and Suggestions - Chris Grindstaff - Christopher Grebs - Daniel Neuhäuser +- Dan Sully - David Lord @davidism - Edmond Burnett - Florent Xicluna diff --git a/CHANGES b/CHANGES index a6dd5300..97459baa 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Version 0.12 ------------ - the cli command now responds to `--version`. +- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``. + See pull request ``#1849``. Version 0.11 ------------ diff --git a/flask/helpers.py b/flask/helpers.py index c744bb8c..e42a6a3c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -437,11 +437,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, to ``True`` to directly emit an ``X-Sendfile`` header. This however requires support of the underlying webserver for ``X-Sendfile``. - By default it will try to guess the mimetype for you, but you can - also explicitly provide one. For extra security you probably want - to send certain files as attachment (HTML for instance). The mimetype - guessing requires a `filename` or an `attachment_filename` to be - provided. + You must explicitly provide the mimetype for the filename or file object. Please never pass filenames to this function from user sources; you should use :func:`send_from_directory` instead. @@ -461,6 +457,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. versionchanged:: 0.9 cache_timeout pulls its default from application config, when None. + .. versionchanged:: 0.12 + mimetype guessing and etag support removed for file objects. + If no mimetype or attachment_filename is provided, application/octet-stream + will be used. + :param filename_or_fp: the filename of the file to send in `latin-1`. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. @@ -488,25 +489,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, filename = filename_or_fp file = None else: - from warnings import warn file = filename_or_fp filename = getattr(file, 'name', None) - # XXX: this behavior is now deprecated because it was unreliable. - # removed in Flask 1.0 - if not attachment_filename and not mimetype \ - and isinstance(filename, string_types): - warn(DeprecationWarning('The filename support for file objects ' - 'passed to send_file is now deprecated. Pass an ' - 'attach_filename if you want mimetypes to be guessed.'), - stacklevel=2) - if add_etags: - warn(DeprecationWarning('In future flask releases etags will no ' - 'longer be generated for file objects passed to the send_file ' - 'function because this behavior was unreliable. Pass ' - 'filenames instead if possible, otherwise attach an etag ' - 'yourself based on another value'), stacklevel=2) - if filename is not None: if not os.path.isabs(filename): filename = os.path.join(current_app.root_path, filename) @@ -553,7 +538,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv.cache_control.max_age = cache_timeout rv.expires = int(time() + cache_timeout) - if add_etags and filename is not None: + if add_etags and filename is not None and file is None: + from warnings import warn + try: rv.set_etag('%s-%s-%s' % ( os.path.getmtime(filename), diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 1fec1d87..8e815ee0 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -349,7 +349,7 @@ class TestSendfile(object): assert rv.mimetype == 'text/html' rv.close() - def test_send_file_object(self, recwarn): + def test_send_file_object(self): app = flask.Flask(__name__) with app.test_request_context(): @@ -361,10 +361,6 @@ class TestSendfile(object): assert rv.mimetype == 'text/html' rv.close() - # mimetypes + etag - recwarn.pop(DeprecationWarning) - recwarn.pop(DeprecationWarning) - app.use_x_sendfile = True with app.test_request_context(): @@ -376,10 +372,6 @@ class TestSendfile(object): os.path.join(app.root_path, 'static/index.html') rv.close() - # mimetypes + etag - recwarn.pop(DeprecationWarning) - recwarn.pop(DeprecationWarning) - app.use_x_sendfile = False with app.test_request_context(): f = StringIO('Test') @@ -389,9 +381,6 @@ class TestSendfile(object): assert rv.mimetype == 'application/octet-stream' rv.close() - # etags - recwarn.pop(DeprecationWarning) - class PyStringIO(object): def __init__(self, *args, **kwargs): self._io = StringIO(*args, **kwargs) @@ -405,11 +394,6 @@ class TestSendfile(object): assert rv.mimetype == 'text/plain' rv.close() - # attachment_filename and etags - a = recwarn.pop(DeprecationWarning) - b = recwarn.pop(DeprecationWarning) - c = recwarn.pop(UserWarning) # file not found - f = StringIO('Test') rv = flask.send_file(f, mimetype='text/plain') rv.direct_passthrough = False @@ -417,9 +401,6 @@ class TestSendfile(object): assert rv.mimetype == 'text/plain' rv.close() - # etags - recwarn.pop(DeprecationWarning) - app.use_x_sendfile = True with app.test_request_context(): @@ -428,10 +409,7 @@ class TestSendfile(object): assert 'x-sendfile' not in rv.headers rv.close() - # etags - recwarn.pop(DeprecationWarning) - - def test_attachment(self, recwarn): + def test_attachment(self): app = flask.Flask(__name__) with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html')) as f: @@ -441,10 +419,6 @@ class TestSendfile(object): assert value == 'attachment' rv.close() - # mimetypes + etag - assert len(recwarn.list) == 2 - recwarn.clear() - with app.test_request_context(): assert options['filename'] == 'index.html' rv = flask.send_file('static/index.html', as_attachment=True) From f034d2e271403c7b3b8a1c2728b2320ed157a037 Mon Sep 17 00:00:00 2001 From: James Farrington Date: Fri, 3 Jun 2016 09:29:12 -0700 Subject: [PATCH 162/440] Tests with and without simplejson for every existing testenv (#1869) --- .travis.yml | 9 +++++++++ tox.ini | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8d2d3c60..0f99a7e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,18 +11,27 @@ python: env: - REQUIREMENTS=lowest + - REQUIREMENTS=lowest-simplejson - REQUIREMENTS=release + - REQUIREMENTS=release-simplejson - REQUIREMENTS=devel + - REQUIREMENTS=devel-simplejson matrix: exclude: # Python 3 support currently does not work with lowest requirements - python: "3.3" env: REQUIREMENTS=lowest + - python: "3.3" + env: REQUIREMENTS=lowest-simplejson - python: "3.4" env: REQUIREMENTS=lowest + - python: "3.4" + env: REQUIREMENTS=lowest-simplejson - python: "3.5" env: REQUIREMENTS=lowest + - python: "3.5" + env: REQUIREMENTS=lowest-simplejson install: diff --git a/tox.ini b/tox.ini index bd936a4b..91a80c19 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,7 @@ [tox] -envlist = {py26,py27,pypy}-{lowest,release,devel}, {py33,py34,py35}-{release,devel} +envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35}-{release,devel}{,-simplejson} + + [testenv] commands = @@ -19,6 +21,7 @@ deps= devel: git+https://github.com/pallets/jinja.git devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git + simplejson: simplejson [testenv:docs] deps = sphinx From fe5f714026b68b1416c3d3985bce5063901c320f Mon Sep 17 00:00:00 2001 From: jphilipsen05 Date: Fri, 3 Jun 2016 09:41:10 -0700 Subject: [PATCH 163/440] fixed unmatched elif (#1872) --- flask/cli.py | 6 +++--- tests/test_cli.py | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index 90eb0353..9dfd339f 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -55,10 +55,10 @@ def prepare_exec_for_file(filename): module = [] # Chop off file extensions or package markers - if filename.endswith('.py'): - filename = filename[:-3] - elif os.path.split(filename)[1] == '__init__.py': + if os.path.split(filename)[1] == '__init__.py': filename = os.path.dirname(filename) + elif filename.endswith('.py'): + filename = filename[:-3] else: raise NoAppException('The file provided (%s) does exist but is not a ' 'valid Python file. This means that it cannot ' diff --git a/tests/test_cli.py b/tests/test_cli.py index 0a479857..1e8feb02 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -19,7 +19,7 @@ from click.testing import CliRunner from flask import Flask, current_app from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ - find_best_app, locate_app, with_appcontext + find_best_app, locate_app, with_appcontext, prepare_exec_for_file def test_cli_name(test_apps): @@ -49,6 +49,13 @@ def test_find_best_app(test_apps): pytest.raises(NoAppException, find_best_app, mod) +def test_prepare_exec_for_file(test_apps): + assert prepare_exec_for_file('test.py') == 'test' + assert prepare_exec_for_file('/usr/share/__init__.py') == 'share' + with pytest.raises(NoAppException): + prepare_exec_for_file('test.txt') + + def test_locate_app(test_apps): """Test of locate_app.""" assert locate_app("cliapp.app").name == "testapp" From 41e08f4ccd0f41896200cbf5c5f5d6f3df3df713 Mon Sep 17 00:00:00 2001 From: Josiah Philipsen Date: Thu, 2 Jun 2016 15:06:55 -0700 Subject: [PATCH 164/440] fixed unmatched elif Also update relevant test --- flask/cli.py | 6 +++--- tests/test_cli.py | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index b94e9317..d3e74d99 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -55,10 +55,10 @@ def prepare_exec_for_file(filename): module = [] # Chop off file extensions or package markers - if filename.endswith('.py'): - filename = filename[:-3] - elif os.path.split(filename)[1] == '__init__.py': + if os.path.split(filename)[1] == '__init__.py': filename = os.path.dirname(filename) + elif filename.endswith('.py'): + filename = filename[:-3] else: raise NoAppException('The file provided (%s) does exist but is not a ' 'valid Python file. This means that it cannot ' diff --git a/tests/test_cli.py b/tests/test_cli.py index 0a479857..1e8feb02 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -19,7 +19,7 @@ from click.testing import CliRunner from flask import Flask, current_app from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ - find_best_app, locate_app, with_appcontext + find_best_app, locate_app, with_appcontext, prepare_exec_for_file def test_cli_name(test_apps): @@ -49,6 +49,13 @@ def test_find_best_app(test_apps): pytest.raises(NoAppException, find_best_app, mod) +def test_prepare_exec_for_file(test_apps): + assert prepare_exec_for_file('test.py') == 'test' + assert prepare_exec_for_file('/usr/share/__init__.py') == 'share' + with pytest.raises(NoAppException): + prepare_exec_for_file('test.txt') + + def test_locate_app(test_apps): """Test of locate_app.""" assert locate_app("cliapp.app").name == "testapp" From 2bde2065e646a67665cb4eaac3219e78524e29ad Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 3 Jun 2016 18:43:32 +0200 Subject: [PATCH 165/440] Changelog for #1872 --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index cbecd4af..771c4fe2 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,14 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.11.1 +-------------- + +Bugfix release, unreleased. + +- Fixed a bug that prevented ``FLASK_APP=foobar/__init__.py`` from working. See + pull request ``#1872``. + Version 0.11 ------------ From e048aa4e19d689104733783a19560a6a485a473c Mon Sep 17 00:00:00 2001 From: dawran6 Date: Fri, 3 Jun 2016 10:58:39 -0700 Subject: [PATCH 166/440] Add negative test for json.jsonify (#1876) Test if jsonify function raises TypeError when both args and kwargs are passed in. Check the TypeError's message --- tests/test_basic.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index 95417c35..55687359 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1031,6 +1031,14 @@ def test_jsonify_mimetype(): assert rv.mimetype == 'application/vnd.api+json' +def test_jsonify_args_and_kwargs_check(): + app = flask.Flask(__name__) + with app.test_request_context(): + with pytest.raises(TypeError) as e: + flask.jsonify('fake args', kwargs='fake') + assert 'behavior undefined' in str(e.value) + + def test_url_generation(): app = flask.Flask(__name__) From bbd6c8c791ffa0f9f30f31d17e47e8fe8e587526 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 3 Jun 2016 14:32:10 -0700 Subject: [PATCH 167/440] Expanding contribution documentation (#1883) - README updated with link to CONTRIBUTING.rst - CONTRIBUTING.rst has instructions on running code coverage --- CONTRIBUTING.rst | 19 +++++++++++++++++++ README | 2 ++ 2 files changed, 21 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 67eb3061..ca7b4af2 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -68,3 +68,22 @@ of ``pytest``. You can install it with:: The ``tox`` command will then run all tests against multiple combinations Python versions and dependency versions. + +Running test coverage +--------------------- +Generating a report of lines that do not have unit test coverage can indicate where +to start contributing. ``pytest`` integrates with ``coverage.py``, using the ``pytest-cov`` +plugin. This assumes you have already run the testsuite (see previous section):: + + pip install pytest-cov + +After this has been installed, you can output a report to the command line using this command:: + + py.test --cov=flask tests/ + +Generate a HTML report can be done using this command:: + + py.test --cov-report html --cov=flask tests/ + +Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io + diff --git a/README b/README index d0e3c521..baea6b24 100644 --- a/README +++ b/README @@ -37,6 +37,8 @@ $ py.test + Details on contributing can be found in CONTRIBUTING.rst + ~ Where can I get help? Either use the #pocoo IRC channel on irc.freenode.net or From 954b7ef7bbc261ac455feb122b56e897653de826 Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Fri, 3 Jun 2016 15:50:38 -0700 Subject: [PATCH 168/440] Enhance code coverage for Blueprint.endpoint Add basic test for the endpoint decorator for the Blueprint object. --- tests/test_blueprints.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index a3309037..de293e7f 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -355,6 +355,25 @@ def test_route_decorator_custom_endpoint_with_dots(): rv = c.get('/py/bar/123') assert rv.status_code == 404 + +def test_endpoint_decorator(): + from werkzeug.routing import Rule + app = flask.Flask(__name__) + app.url_map.add(Rule('/foo', endpoint='bar')) + + bp = flask.Blueprint('bp', __name__) + + @bp.endpoint('bar') + def foobar(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix='/bp_prefix') + + c = app.test_client() + assert c.get('/foo').data == b'bar' + assert c.get('/bp_prefix/bar').status_code == 404 + + def test_template_filter(): bp = flask.Blueprint('bp', __name__) @bp.app_template_filter() From 9c236d3b84332aef0c834266a0e3d2171fe5a7bd Mon Sep 17 00:00:00 2001 From: RamiC Date: Sat, 4 Jun 2016 09:25:16 +0300 Subject: [PATCH 169/440] Mention the template name conflict issue in blueprint templates docs (#1843) * Mention the template name conflict issue in docs. In the blueprints templates documentation mention the possible templates name conflict issue re #266 * Mention priorities between blueprints and other rephrasing fixes --- docs/blueprints.rst | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index d22220b2..89d3701e 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -176,16 +176,25 @@ the `template_folder` parameter to the :class:`Blueprint` constructor:: admin = Blueprint('admin', __name__, template_folder='templates') -As for static files, the path can be absolute or relative to the blueprint -resource folder. The template folder is added to the searchpath of -templates but with a lower priority than the actual application's template -folder. That way you can easily override templates that a blueprint -provides in the actual application. +For static files, the path can be absolute or relative to the blueprint +resource folder. + +The template folder is added to the search path of templates but with a lower +priority than the actual application's template folder. That way you can +easily override templates that a blueprint provides in the actual application. +This also means that if you don't want a blueprint template to be accidentally +overridden, make sure that no other blueprint or actual application template +has the same relative path. When multiple blueprints provide the same relative +template path the first blueprint registered takes precedence over the others. + So if you have a blueprint in the folder ``yourapplication/admin`` and you want to render the template ``'admin/index.html'`` and you have provided ``templates`` as a `template_folder` you will have to create a file like -this: :file:`yourapplication/admin/templates/admin/index.html`. +this: :file:`yourapplication/admin/templates/admin/index.html`. The reason +for the extra ``admin`` folder is to avoid getting our template overridden +by a template named ``index.html`` in the actual application template +folder. To further reiterate this: if you have a blueprint named ``admin`` and you want to render a template called :file:`index.html` which is specific to this From 03ea11fe76a593c146c347f49d8285efb36bb886 Mon Sep 17 00:00:00 2001 From: Giampaolo Eusebi Date: Sat, 4 Jun 2016 11:26:16 +0200 Subject: [PATCH 170/440] Make safe_join able to safely join multiple paths --- CHANGES | 2 ++ flask/helpers.py | 32 ++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index bc554652..2bedbd15 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,8 @@ Version 0.12 - the cli command now responds to `--version`. - Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``. See pull request ``#1849``. +- Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` + (pull request ``#1730``). Version 0.11.1 -------------- diff --git a/flask/helpers.py b/flask/helpers.py index e42a6a3c..ff660d72 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -563,8 +563,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, return rv -def safe_join(directory, filename): - """Safely join `directory` and `filename`. +def safe_join(directory, *pathnames): + """Safely join `directory` and zero or more untrusted `pathnames` + components. Example usage:: @@ -574,20 +575,23 @@ def safe_join(directory, filename): with open(filename, 'rb') as fd: content = fd.read() # Read and process the file content... - :param directory: the base directory. - :param filename: the untrusted filename relative to that directory. - :raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path - would fall out of `directory`. + :param directory: the trusted base directory. + :param pathnames: the untrusted pathnames relative to that directory. + :raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed + paths fall out of its boundaries. """ - filename = posixpath.normpath(filename) - for sep in _os_alt_seps: - if sep in filename: + for filename in pathnames: + if filename != '': + filename = posixpath.normpath(filename) + for sep in _os_alt_seps: + if sep in filename: + raise NotFound() + if os.path.isabs(filename) or \ + filename == '..' or \ + filename.startswith('../'): raise NotFound() - if os.path.isabs(filename) or \ - filename == '..' or \ - filename.startswith('../'): - raise NotFound() - return os.path.join(directory, filename) + directory = os.path.join(directory, filename) + return directory def send_from_directory(directory, filename, **options): From 06a170ea9b73ffe4f2e64453c70ed6b44619ecc8 Mon Sep 17 00:00:00 2001 From: Giampaolo Eusebi Date: Sat, 4 Jun 2016 11:26:44 +0200 Subject: [PATCH 171/440] Add tests for safe_join --- tests/test_helpers.py | 44 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8e815ee0..a06fc3d1 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -15,7 +15,7 @@ import os import datetime import flask from logging import StreamHandler -from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date from flask._compat import StringIO, text_type @@ -722,3 +722,45 @@ class TestStreaming(object): rv = c.get('/?name=World') assert rv.data == b'Hello World!' assert called == [42] + + +class TestSafeJoin(object): + + def test_safe_join(self): + # Valid combinations of *args and expected joined paths. + passing = ( + (('a/b/c', ), 'a/b/c'), + (('/', 'a/', 'b/', 'c/', ), '/a/b/c'), + (('a', 'b', 'c', ), 'a/b/c'), + (('/a', 'b/c', ), '/a/b/c'), + (('a/b', 'X/../c'), 'a/b/c', ), + (('/a/b', 'c/X/..'), '/a/b/c', ), + # If last path is '' add a slash + (('/a/b/c', '', ), '/a/b/c/', ), + # Preserve dot slash + (('/a/b/c', './', ), '/a/b/c/.', ), + (('a/b/c', 'X/..'), 'a/b/c/.', ), + # Base directory is always considered safe + (('../', 'a/b/c'), '../a/b/c'), + (('/..', ), '/..'), + ) + + for args, expected in passing: + assert flask.safe_join(*args) == expected + + def test_safe_join_exceptions(self): + # Should raise werkzeug.exceptions.NotFound on unsafe joins. + failing = ( + # path.isabs and ``..'' checks + ('/a', 'b', '/c'), + ('/a', '../b/c', ), + ('/a', '..', 'b/c'), + # Boundaries violations after path normalization + ('/a', 'b/../b/../../c', ), + ('/a', 'b', 'c/../..'), + ('/a', 'b/../../c', ), + ) + + for args in failing: + with pytest.raises(NotFound): + print(flask.safe_join(*args)) From 64a37bb9b758630c0f2c649d82e10e849c095d48 Mon Sep 17 00:00:00 2001 From: Hyunchel Kim Date: Sun, 5 Jun 2016 10:32:00 -0700 Subject: [PATCH 172/440] Test side effect (#1889) Function `prepare_exec_for_file` has a side effect where a path is added to `sys.path` list. This commit enhances an exisiting test case for `prepare_exec_for_file` by testing the side effect of the function and adding necessary comments. --- tests/test_cli.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 1e8feb02..c4be45e6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -12,6 +12,8 @@ # Copyright (C) 2015 CERN. # from __future__ import absolute_import, print_function +import os +import sys import click import pytest @@ -50,10 +52,23 @@ def test_find_best_app(test_apps): def test_prepare_exec_for_file(test_apps): - assert prepare_exec_for_file('test.py') == 'test' - assert prepare_exec_for_file('/usr/share/__init__.py') == 'share' + """Expect the correct path to be set and the correct module name to be returned. + + :func:`prepare_exec_for_file` has a side effect, where + the parent directory of given file is added to `sys.path`. + """ + realpath = os.path.realpath('/tmp/share/test.py') + dirname = os.path.dirname(realpath) + assert prepare_exec_for_file('/tmp/share/test.py') == 'test' + assert dirname in sys.path + + realpath = os.path.realpath('/tmp/share/__init__.py') + dirname = os.path.dirname(os.path.dirname(realpath)) + assert prepare_exec_for_file('/tmp/share/__init__.py') == 'share' + assert dirname in sys.path + with pytest.raises(NoAppException): - prepare_exec_for_file('test.txt') + prepare_exec_for_file('/tmp/share/test.txt') def test_locate_app(test_apps): From af515cc7ea561292b07195c6a93662b7ca189e21 Mon Sep 17 00:00:00 2001 From: Prachi Shirish Khadke Date: Thu, 2 Jun 2016 13:00:42 -0700 Subject: [PATCH 173/440] Add last_modified arg for send_file Enhancement: Add last_modified arg of type DateTime to send_file. Fixes pallets/flask#1321 --- flask/helpers.py | 13 ++++++++++--- tests/test_helpers.py | 13 ++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index ff660d72..37c458f5 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -39,7 +39,7 @@ 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 @@ -429,7 +429,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]): def send_file(filename_or_fp, mimetype=None, as_attachment=False, attachment_filename=None, add_etags=True, - cache_timeout=None, conditional=False): + cache_timeout=None, conditional=False, last_modified=None): """Sends the contents of a file to the client. This will use the most efficient method available and configured. By default it will try to use the WSGI server's file_wrapper support. Alternatively @@ -483,6 +483,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, (default), this value is set by :meth:`~Flask.get_send_file_max_age` of :data:`~flask.current_app`. + :param last_modified: the Datetime object representing timestamp for when + the input file was last modified. """ mtime = None if isinstance(filename_or_fp, string_types): @@ -528,7 +530,12 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, # if we know the file modification date, we can store it as # the time of the last modification. - if mtime is not None: + if last_modified is not None: + if PY2: + rv.last_modified = int(last_modified.strftime("%s")) + else: + rv.last_modified = last_modified.timestamp() + elif mtime is not None: rv.last_modified = int(mtime) rv.cache_control.public = True diff --git a/tests/test_helpers.py b/tests/test_helpers.py index a06fc3d1..9a07e0b9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -18,7 +18,7 @@ from logging import StreamHandler from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date -from flask._compat import StringIO, text_type +from flask._compat import StringIO, text_type, PY2 def has_encoding(name): @@ -349,6 +349,17 @@ class TestSendfile(object): assert rv.mimetype == 'text/html' rv.close() + def test_send_file_last_modified(self): + app = flask.Flask(__name__) + with app.test_request_context(): + dtm = datetime.datetime.now() + rv = flask.send_file('static/index.html', last_modified=dtm) + if PY2: + assert rv.last_modified == int(dtm.strftime("%s")) + else: + assert rv.last_modified == dtm.timestamp() + rv.close() + def test_send_file_object(self): app = flask.Flask(__name__) From 7c271401b284e6fcc2040fffe317342e2a17a902 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 5 Jun 2016 12:42:34 -0700 Subject: [PATCH 174/440] pass value directly to last_modified --- flask/helpers.py | 14 +++++--------- tests/test_helpers.py | 17 +++++++++-------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 37c458f5..4129ed30 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -483,8 +483,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, (default), this value is set by :meth:`~Flask.get_send_file_max_age` of :data:`~flask.current_app`. - :param last_modified: the Datetime object representing timestamp for when - the input file was last modified. + :param last_modified: set the ``Last-Modified`` header to this value, + a :class:`~datetime.datetime` or timestamp. + If a file was passed, this overrides its mtime. """ mtime = None if isinstance(filename_or_fp, string_types): @@ -528,15 +529,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv = current_app.response_class(data, mimetype=mimetype, headers=headers, direct_passthrough=True) - # if we know the file modification date, we can store it as - # the time of the last modification. if last_modified is not None: - if PY2: - rv.last_modified = int(last_modified.strftime("%s")) - else: - rv.last_modified = last_modified.timestamp() + rv.last_modified = last_modified elif mtime is not None: - rv.last_modified = int(mtime) + rv.last_modified = mtime rv.cache_control.public = True if cache_timeout is None: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 9a07e0b9..3ff5900b 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -351,14 +351,15 @@ class TestSendfile(object): def test_send_file_last_modified(self): app = flask.Flask(__name__) - with app.test_request_context(): - dtm = datetime.datetime.now() - rv = flask.send_file('static/index.html', last_modified=dtm) - if PY2: - assert rv.last_modified == int(dtm.strftime("%s")) - else: - assert rv.last_modified == dtm.timestamp() - rv.close() + last_modified = datetime.datetime(1999, 1, 1) + + @app.route('/') + def index(): + return flask.send_file(StringIO("party like it's"), last_modified=last_modified) + + c = app.test_client() + rv = c.get('/') + assert rv.last_modified == last_modified def test_send_file_object(self): app = flask.Flask(__name__) From 33212309a22d3aeed725e3138d48593dc0a5b47f Mon Sep 17 00:00:00 2001 From: Shawn McElroy Date: Thu, 2 Jun 2016 15:58:14 -0700 Subject: [PATCH 175/440] updating docs and showing how to use `setup.cfg` to configure dev builds with sdist --- docs/patterns/fabric.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/patterns/fabric.rst b/docs/patterns/fabric.rst index 7270a569..4ba667a1 100644 --- a/docs/patterns/fabric.rst +++ b/docs/patterns/fabric.rst @@ -156,6 +156,25 @@ location where it's expected (eg: :file:`/var/www/yourapplication`). Either way, in our case here we only expect one or two servers and we can upload them ahead of time by hand. +Configuring egg_info +-------------------- +If you need to configure your fabric build with tags, you can create a `setup.cfg` +file in the root of your app. An example would be: + + [egg_info] + tag_svn_revision = 1 + tag_build = .dev + tag_date = 1 + + [aliases] + release = egg_info -RDb '' + +And now when running `python setup.py sdist ...` in your fabric file, it will +pick up on these settings and tag appropriately. And when making a release build +it will ignore these build tags as expected. + + + First Deployment ---------------- From 14a5a9e5547895f9e77dfd2dccd7fdb63fe2b8da Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 5 Jun 2016 12:59:04 -0700 Subject: [PATCH 176/440] move setup.cfg info to setuptools docs, reword --- docs/patterns/distribute.rst | 19 +++++++++++++++++++ docs/patterns/fabric.rst | 18 ------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index 872d6e01..8f79a2dd 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -87,6 +87,25 @@ your packages to be installed as zip files because some tools do not support them and they make debugging a lot harder. +Tagging Builds +-------------- + +It is useful to distinguish between release and development builds. Add a +:file:`setup.cfg` file to configure these options. + + [egg_info] + tag_build = .dev + tag_date = 1 + + [aliases] + release = egg_info -RDb '' + +Running ``python setup.py sdist`` will create a development package +with ".dev" and the current date appended: ``flaskr-1.0.dev20160314.tar.gz``. +Running ``python setup.py release sdist`` will create a release package +with only the version: ``flaskr-1.0.tar.gz``. + + .. _distributing-resources: Distributing Resources diff --git a/docs/patterns/fabric.rst b/docs/patterns/fabric.rst index 4ba667a1..f6ae0330 100644 --- a/docs/patterns/fabric.rst +++ b/docs/patterns/fabric.rst @@ -156,24 +156,6 @@ location where it's expected (eg: :file:`/var/www/yourapplication`). Either way, in our case here we only expect one or two servers and we can upload them ahead of time by hand. -Configuring egg_info --------------------- -If you need to configure your fabric build with tags, you can create a `setup.cfg` -file in the root of your app. An example would be: - - [egg_info] - tag_svn_revision = 1 - tag_build = .dev - tag_date = 1 - - [aliases] - release = egg_info -RDb '' - -And now when running `python setup.py sdist ...` in your fabric file, it will -pick up on these settings and tag appropriately. And when making a release build -it will ignore these build tags as expected. - - First Deployment ---------------- From aa9a994946acf45187cb12506b47433306aa3473 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 5 Jun 2016 13:12:25 -0700 Subject: [PATCH 177/440] use pip instead of setup.py in fabric command --- docs/patterns/fabric.rst | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/docs/patterns/fabric.rst b/docs/patterns/fabric.rst index f6ae0330..3dbf2146 100644 --- a/docs/patterns/fabric.rst +++ b/docs/patterns/fabric.rst @@ -43,36 +43,25 @@ virtual environment:: env.hosts = ['server1.example.com', 'server2.example.com'] def pack(): - # create a new source distribution as tarball + # build the package local('python setup.py sdist --formats=gztar', capture=False) def deploy(): - # figure out the release name and version + # figure out the package name and version dist = local('python setup.py --fullname', capture=True).strip() - # upload the source tarball to the temporary folder on the server - put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz') - # create a place where we can unzip the tarball, then enter - # that directory and unzip it - run('mkdir /tmp/yourapplication') - with cd('/tmp/yourapplication'): - run('tar xzf /tmp/yourapplication.tar.gz') - # now setup the package with our virtual environment's - # python interpreter - run('/var/www/yourapplication/env/bin/python setup.py install') - # now that all is set up, delete the folder again - run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz') - # and finally touch the .wsgi file so that mod_wsgi triggers - # a reload of the application - run('touch /var/www/yourapplication.wsgi') + filename = '%s.tar.gz' % dist + + # upload the package to the temporary folder on the server + put('dist/%s' % filename, '/tmp/%s' % filename) -The example above is well documented and should be straightforward. Here -a recap of the most common commands fabric provides: + # install the package in the application's virtualenv with pip + run('/var/www/yourapplication/env/bin/pip install /tmp/%s' % filename) -- `run` - executes a command on a remote server -- `local` - executes a command on the local machine -- `put` - uploads a file to the remote server -- `cd` - changes the directory on the serverside. This has to be used - in combination with the ``with`` statement. + # remove the uploaded package + run('rm -r /tmp/%s' % filename) + + # touch the .wsgi file to trigger a reload in mod_wsgi + run('touch /var/www/yourapplication.wsgi') Running Fabfiles ---------------- From 434c19933eb445907d73b9bb9e4973d546f5ee52 Mon Sep 17 00:00:00 2001 From: Ping Hu Date: Thu, 2 Jun 2016 11:12:35 -0700 Subject: [PATCH 178/440] Add clarification for login_required decorator ref #313 --- docs/patterns/viewdecorators.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/patterns/viewdecorators.rst b/docs/patterns/viewdecorators.rst index 97e6871d..0ddda7d9 100644 --- a/docs/patterns/viewdecorators.rst +++ b/docs/patterns/viewdecorators.rst @@ -39,7 +39,12 @@ logged in:: So how would you use that decorator now? Apply it as innermost decorator to a view function. When applying further decorators, always remember -that the :meth:`~flask.Flask.route` decorator is the outermost:: +that the :meth:`~flask.Flask.route` decorator is the outermost. + +While the ``next`` value may exist in ``request.args`` after a ``GET`` request for +the login form, you'll have to pass it along when sending the ``POST`` request +from the login form. You can do this with a hidden input tag and ``requests.values`` +or ``requests.form``.:: @app.route('/secret_page') @login_required From 169a4e0c44dcf8414d7cc27d405e9afdcd29a53c Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 5 Jun 2016 14:21:17 -0700 Subject: [PATCH 179/440] move note about next param, add example, other cleanup --- docs/patterns/viewdecorators.rst | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/patterns/viewdecorators.rst b/docs/patterns/viewdecorators.rst index 0ddda7d9..7fd97dca 100644 --- a/docs/patterns/viewdecorators.rst +++ b/docs/patterns/viewdecorators.rst @@ -16,15 +16,13 @@ Login Required Decorator ------------------------ So let's implement such a decorator. A decorator is a function that -returns a function. Pretty simple actually. The only thing you have to -keep in mind when implementing something like this is to update the -`__name__`, `__module__` and some other attributes of a function. This is -often forgotten, but you don't have to do that by hand, there is a -function for that that is used like a decorator (:func:`functools.wraps`). +wraps and replaces another function. Since the original function is +replaced, you need to remember to copy the original function's information +to the new function. Use :func:`functools.wraps` to handle this for you. This example assumes that the login page is called ``'login'`` and that -the current user is stored as `g.user` and ``None`` if there is no-one -logged in:: +the current user is stored in ``g.user`` and is ``None`` if there is no-one +logged in. :: from functools import wraps from flask import g, request, redirect, url_for @@ -37,20 +35,24 @@ logged in:: return f(*args, **kwargs) return decorated_function -So how would you use that decorator now? Apply it as innermost decorator -to a view function. When applying further decorators, always remember -that the :meth:`~flask.Flask.route` decorator is the outermost. - -While the ``next`` value may exist in ``request.args`` after a ``GET`` request for -the login form, you'll have to pass it along when sending the ``POST`` request -from the login form. You can do this with a hidden input tag and ``requests.values`` -or ``requests.form``.:: +To use the decorator, apply it as innermost decorator to a view function. +When applying further decorators, always remember +that the :meth:`~flask.Flask.route` decorator is the outermost. :: @app.route('/secret_page') @login_required def secret_page(): pass +.. note:: + The ``next`` value will exist in ``request.args`` after a ``GET`` request for + the login page. You'll have to pass it along when sending the ``POST`` request + from the login form. You can do this with a hidden input tag, then retrieve it + from ``request.form`` when logging the user in. :: + + + + Caching Decorator ----------------- From 00c200eeaa6af7c5c25c37471ab0313ae26651b4 Mon Sep 17 00:00:00 2001 From: alatar- Date: Thu, 2 Jun 2016 15:27:14 -0700 Subject: [PATCH 180/440] Update documentation about python 3 support in Flask, resolves #1578 --- docs/advanced_foreword.rst | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst index 22e333f8..07705952 100644 --- a/docs/advanced_foreword.rst +++ b/docs/advanced_foreword.rst @@ -46,24 +46,10 @@ spam, links to malicious software, and the like. Flask is no different from any other framework in that you the developer must build with caution, watching for exploits when building to your requirements. -The Status of Python 3 +Python 3 Support in Flask ---------------------- -Currently the Python community is in the process of improving libraries to -support the new iteration of the Python programming language. While the -situation is greatly improving there are still some issues that make it -hard for users to switch over to Python 3 just now. These problems are -partially caused by changes in the language that went unreviewed for too -long, partially also because we have not quite worked out how the lower- -level API should change to account for the Unicode differences in Python 3. - -We strongly recommend using Python 2.7 with activated Python 3 -warnings during development. If you plan on upgrading to Python 3 in the -near future we strongly recommend that you read `How to write forwards -compatible Python code -`_. - -If you do want to dive into Python 3 already have a look at the +If you think of using Flask with Python 3 have a look at the :ref:`python3-support` page. Continue to :ref:`installation` or the :ref:`quickstart`. From baa2689658e340fc5a2a093bb061f3b9af6d85b6 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 5 Jun 2016 15:45:39 -0700 Subject: [PATCH 181/440] clean up py3 info more --- docs/advanced_foreword.rst | 6 +++--- docs/python3.rst | 40 ++++++++++++++------------------------ 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst index 07705952..82b3dc58 100644 --- a/docs/advanced_foreword.rst +++ b/docs/advanced_foreword.rst @@ -47,9 +47,9 @@ 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 ----------------------- +------------------------- -If you think of using Flask with Python 3 have a look at the -:ref:`python3-support` page. +Flask, its dependencies, and most Flask extensions all support Python 3. +If you want to use Flask with Python 3 have a look at the :ref:`python3-support` page. Continue to :ref:`installation` or the :ref:`quickstart`. diff --git a/docs/python3.rst b/docs/python3.rst index 4d488f16..61ef3eaa 100644 --- a/docs/python3.rst +++ b/docs/python3.rst @@ -3,32 +3,22 @@ Python 3 Support ================ -Flask and all of its dependencies support Python 3 so you can in theory -start working on it already. There are however a few things you should be -aware of before you start using Python 3 for your next project. +Flask, its dependencies, and most Flask extensions support Python 3. +You should start using Python 3 for your next project, +but there are a few things to be aware of. -If you want to use Flask with Python 3 you will need to use Python 3.3 or -higher. 3.2 and older are *not* supported. +You need to use Python 3.3 or higher. 3.2 and older are *not* supported. -In addition to that you need to use the latest and greatest versions of -`itsdangerous`, `Jinja2` and `Werkzeug`. Flask 0.10 and Werkzeug 0.9 were -the first versions to introduce Python 3 support. +You should use the latest versions of all Flask-related packages. +Flask 0.10 and Werkzeug 0.9 were the first versions to introduce Python 3 support. -Some of the decisions made in regards to unicode and byte utilization on -Python 3 make it hard to write low level code. This mainly affects WSGI -middlewares and interacting with the WSGI provided information. Werkzeug -wraps all that information in high-level helpers but some of those were -specifically added for the Python 3 support and are quite new. +Python 3 changed how unicode and bytes are handled, +which complicated 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 effect you. -Unless you require absolute compatibility, you should be fine with Python 3 -nowadays. Most libraries and Flask extensions have been ported by now and -using Flask with Python 3 is generally a smooth ride. However, keep in mind -that most libraries (including Werkzeug and Flask) might not quite as stable -on Python 3 yet. You might therefore sometimes run into bugs that are -usually encoding-related. - -The majority of the upgrade pain is in the lower-level libraries like -Flask and Werkzeug and not in the actual high-level application code. For -instance all of the Flask examples that are in the Flask repository work -out of the box on both 2.x and 3.x and did not require a single line of -code changed. +The majority of the upgrade work is in the lower-level libraries like +Flask and Werkzeug, not the high-level application code. +For example, all of the examples in the Flask repository work on both Python 2 and 3 +and did not require a single line of code changed. From 3384813151e18ea7903a31f22bff1ea65dcb6bee Mon Sep 17 00:00:00 2001 From: Ryan Backman Date: Mon, 6 Jun 2016 04:03:01 -0700 Subject: [PATCH 182/440] Clarify wording in App Context documentation. (#1890) --- docs/appcontext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index baa71315..2ccacc8c 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -74,7 +74,7 @@ The application context is also used by the :func:`~flask.url_for` function in case a ``SERVER_NAME`` was configured. This allows you to generate URLs even in the absence of a request. -If a request context has not been pushed and an application context has +If no request context has been pushed and an application context has not been explicitly set, a ``RuntimeError`` will be raised. :: RuntimeError: Working outside of application context. From f6b5b571dce6d8ed1632622f1cdea3acd2fd1a57 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 6 Jun 2016 13:39:14 +0200 Subject: [PATCH 183/440] Fix several typos in python3.rst - complicated -> complicates, since the effect continues into the future. See #1891 - effect -> affect - Rewrap paragraph --- docs/python3.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/python3.rst b/docs/python3.rst index 61ef3eaa..a7a4f165 100644 --- a/docs/python3.rst +++ b/docs/python3.rst @@ -12,11 +12,10 @@ 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 complicated 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 effect you. +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. From 5aa70b5a56f10b6077885c45697ee29e98bd345e Mon Sep 17 00:00:00 2001 From: avborhanian Date: Mon, 6 Jun 2016 11:29:14 -0400 Subject: [PATCH 184/440] Updating url in errorhandling.rst to a valid link Was originally set to github.com/getsentry/sentry, which was a relative link to a page that doesn't exist. I prepended https:// to fix this problem. --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index e2af7af4..2dc7fafe 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -36,7 +36,7 @@ overwhelming if enough users are hitting the error and log files are typically never looked at. This is why we recommend using `Sentry `_ for dealing with application errors. It's available as an Open Source project `on GitHub -`__ and is also available as a `hosted version +`__ and is also available as a `hosted version `_ which you can try for free. Sentry aggregates duplicate errors, captures the full stack trace and local variables for debugging, and sends you mails based on new errors or From 5eaed3711685f263d4a34af25b5976f04e6885f2 Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Tue, 7 Jun 2016 08:03:55 -0400 Subject: [PATCH 185/440] Add test for find_default_import_path --- tests/test_cli.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index c4be45e6..4a3d0831 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -21,7 +21,8 @@ from click.testing import CliRunner from flask import Flask, current_app from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ - find_best_app, locate_app, with_appcontext, prepare_exec_for_file + find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \ + find_default_import_path def test_cli_name(test_apps): @@ -79,6 +80,19 @@ def test_locate_app(test_apps): pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") +def test_find_default_import_path(test_apps, monkeypatch, tmpdir): + """Test of find_default_import_path.""" + monkeypatch.delitem(os.environ, 'FLASK_APP', raising=False) + assert find_default_import_path() == None + monkeypatch.setitem(os.environ, 'FLASK_APP', 'notanapp') + assert find_default_import_path() == 'notanapp' + tmpfile = tmpdir.join('testapp.py') + tmpfile.write('') + monkeypatch.setitem(os.environ, 'FLASK_APP', str(tmpfile)) + expect_rv = prepare_exec_for_file(str(tmpfile)) + assert find_default_import_path() == expect_rv + + def test_scriptinfo(test_apps): """Test of ScriptInfo.""" obj = ScriptInfo(app_import_path="cliapp.app:testapp") From d1d82ca8ce7262ad9d27245ce44f86571287810e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 7 Jun 2016 18:22:43 +0200 Subject: [PATCH 186/440] Bump version to 0.11.1 --- CHANGES | 2 +- flask/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 771c4fe2..c057a675 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. Version 0.11.1 -------------- -Bugfix release, unreleased. +Bugfix release, released on June 7th 2016. - Fixed a bug that prevented ``FLASK_APP=foobar/__init__.py`` from working. See pull request ``#1872``. diff --git a/flask/__init__.py b/flask/__init__.py index f3d5a090..70d5e589 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12-dev' +__version__ = '0.11.1' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 724f04d30597a6d0144958e86f20e816b0d9b1f1 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 7 Jun 2016 18:23:09 +0200 Subject: [PATCH 187/440] This is 0.11.2-dev --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 70d5e589..509b944f 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.11.1' +__version__ = '0.11.2-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 501b8590dd8b262c93d52c92ab4b743af1f5d317 Mon Sep 17 00:00:00 2001 From: RamiC Date: Wed, 8 Jun 2016 12:03:26 +0300 Subject: [PATCH 188/440] Allow per blueprint json encoder decoder re #1710 --- flask/blueprints.py | 7 +++++++ flask/json.py | 6 ++++-- tests/test_helpers.py | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/flask/blueprints.py b/flask/blueprints.py index 586a1b0b..62675204 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -89,6 +89,13 @@ class Blueprint(_PackageBoundObject): warn_on_modifications = False _got_registered_once = False + #: Blueprint local JSON decoder class to use. + # Set to None to use the :class:`~flask.app.Flask.json_encoder`. + json_encoder = None + #: Blueprint local JSON decoder class to use. + # Set to None to use the :class:`~flask.app.Flask.json_decoder`. + json_decoder = None + def __init__(self, name, import_name, static_folder=None, static_url_path=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, diff --git a/flask/json.py b/flask/json.py index b9ce4a08..ad0e7bdf 100644 --- a/flask/json.py +++ b/flask/json.py @@ -94,7 +94,8 @@ class JSONDecoder(_json.JSONDecoder): def _dump_arg_defaults(kwargs): """Inject default arguments for dump functions.""" if current_app: - kwargs.setdefault('cls', current_app.json_encoder) + bp = current_app.blueprints.get(request.blueprint, None) + kwargs.setdefault('cls', bp.json_encoder if bp else current_app.json_encoder) if not current_app.config['JSON_AS_ASCII']: kwargs.setdefault('ensure_ascii', False) kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS']) @@ -106,7 +107,8 @@ def _dump_arg_defaults(kwargs): def _load_arg_defaults(kwargs): """Inject default arguments for load functions.""" if current_app: - kwargs.setdefault('cls', current_app.json_decoder) + bp = current_app.blueprints.get(request.blueprint, None) + kwargs.setdefault('cls', bp.json_decoder if bp else current_app.json_decoder) else: kwargs.setdefault('cls', JSONDecoder) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3ff5900b..e893004a 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -90,12 +90,12 @@ class TestJSON(object): app = flask.Flask(__name__) app.config['JSON_AS_ASCII'] = True - with app.app_context(): + with app.test_request_context(): rv = flask.json.dumps(u'\N{SNOWMAN}') assert rv == '"\\u2603"' app.config['JSON_AS_ASCII'] = False - with app.app_context(): + with app.test_request_context(): rv = flask.json.dumps(u'\N{SNOWMAN}') assert rv == u'"\u2603"' @@ -234,6 +234,41 @@ class TestJSON(object): }), content_type='application/json') assert rv.data == b'"<42>"' + def test_blueprint_json_customization(self): + class X(object): + def __init__(self, val): + self.val = val + class MyEncoder(flask.json.JSONEncoder): + def default(self, o): + if isinstance(o, X): + return '<%d>' % o.val + return flask.json.JSONEncoder.default(self, o) + class MyDecoder(flask.json.JSONDecoder): + def __init__(self, *args, **kwargs): + kwargs.setdefault('object_hook', self.object_hook) + flask.json.JSONDecoder.__init__(self, *args, **kwargs) + def object_hook(self, obj): + if len(obj) == 1 and '_foo' in obj: + return X(obj['_foo']) + return obj + + blue = flask.Blueprint('blue', __name__) + blue.json_encoder = MyEncoder + blue.json_decoder = MyDecoder + @blue.route('/bp', methods=['POST']) + def index(): + return flask.json.dumps(flask.request.get_json()['x']) + + app = flask.Flask(__name__) + app.testing = True + app.register_blueprint(blue) + + c = app.test_client() + rv = c.post('/bp', data=flask.json.dumps({ + 'x': {'_foo': 42} + }), content_type='application/json') + assert rv.data == b'"<42>"' + def test_modified_url_encoding(self): class ModifiedRequest(flask.Request): url_charset = 'euc-kr' From 4305ebdf6692956a7e21a7195b90505d92f559d8 Mon Sep 17 00:00:00 2001 From: RamiC Date: Wed, 8 Jun 2016 12:50:43 +0300 Subject: [PATCH 189/440] Check for a request ctx before using the request. Use the app json coder when blueprint json coder is set to none. Revert the failling test to using an app_context re #1710 --- flask/json.py | 15 +++++++++++---- tests/test_helpers.py | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/flask/json.py b/flask/json.py index ad0e7bdf..fd39e539 100644 --- a/flask/json.py +++ b/flask/json.py @@ -13,6 +13,7 @@ import uuid from datetime import date from .globals import current_app, request from ._compat import text_type, PY2 +from .ctx import has_request_context from werkzeug.http import http_date from jinja2 import Markup @@ -94,8 +95,11 @@ class JSONDecoder(_json.JSONDecoder): def _dump_arg_defaults(kwargs): """Inject default arguments for dump functions.""" if current_app: - bp = current_app.blueprints.get(request.blueprint, None) - kwargs.setdefault('cls', bp.json_encoder if bp else current_app.json_encoder) + bp = current_app.blueprints.get(request.blueprint, + None) if has_request_context() else None + kwargs.setdefault('cls', + bp.json_encoder if bp and bp.json_encoder + else current_app.json_encoder) if not current_app.config['JSON_AS_ASCII']: kwargs.setdefault('ensure_ascii', False) kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS']) @@ -107,8 +111,11 @@ def _dump_arg_defaults(kwargs): def _load_arg_defaults(kwargs): """Inject default arguments for load functions.""" if current_app: - bp = current_app.blueprints.get(request.blueprint, None) - kwargs.setdefault('cls', bp.json_decoder if bp else current_app.json_decoder) + bp = current_app.blueprints.get(request.blueprint, + None) if has_request_context() else None + kwargs.setdefault('cls', + bp.json_decoder if bp and bp.json_decoder + else current_app.json_decoder) else: kwargs.setdefault('cls', JSONDecoder) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index e893004a..142f555f 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -90,12 +90,12 @@ class TestJSON(object): app = flask.Flask(__name__) app.config['JSON_AS_ASCII'] = True - with app.test_request_context(): + with app.app_context(): rv = flask.json.dumps(u'\N{SNOWMAN}') assert rv == '"\\u2603"' app.config['JSON_AS_ASCII'] = False - with app.test_request_context(): + with app.app_context(): rv = flask.json.dumps(u'\N{SNOWMAN}') assert rv == u'"\u2603"' From 5cadd4a348b4d42e22ddb2bfad93f74a4f34cc59 Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Wed, 8 Jun 2016 08:26:01 -0400 Subject: [PATCH 190/440] Added make target for test coverage, documented make commands --- .gitignore | 2 ++ CONTRIBUTING.rst | 25 ++++++++++++++++--------- Makefile | 5 +++++ test-requirements.txt | 2 ++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 9bf4f063..e754af19 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ _mailinglist .tox .cache/ .idea/ +.coverage +htmlcov diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ca7b4af2..f4d1ef9c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -31,18 +31,12 @@ Submitting patches - Try to follow `PEP8 `_, but you may ignore the line-length-limit if following it would make the code uglier. - -Running the testsuite ---------------------- +Getting Started +=============== You probably want to set up a `virtualenv `_. -The minimal requirement for running the testsuite is ``py.test``. You can -install it with:: - - pip install pytest - Clone this repository:: git clone https://github.com/pallets/flask.git @@ -52,11 +46,21 @@ Install Flask as an editable package using the current source:: cd flask pip install --editable . +Running the testsuite +--------------------- + +The minimal requirement for running the testsuite is ``pytest``. You can +install it with:: + + pip install pytest + Then you can run the testsuite with:: py.test -With only py.test installed, a large part of the testsuite will get skipped +**Shortcut**: ``make test`` will ensure ``pytest`` is installed, and run it. + +With only pytest installed, a large part of the testsuite will get skipped though. Whether this is relevant depends on which part of Flask you're working on. Travis is set up to run the full testsuite when you submit your pull request anyways. @@ -69,6 +73,8 @@ of ``pytest``. You can install it with:: The ``tox`` command will then run all tests against multiple combinations Python versions and dependency versions. +**Shortcut**: ``make tox-test`` will ensure ``tox`` is installed, and run it. + Running test coverage --------------------- Generating a report of lines that do not have unit test coverage can indicate where @@ -87,3 +93,4 @@ Generate a HTML report can be done using this command:: Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io +**Shortcut**: ``make cov`` will ensure ``pytest-cov`` is installed, run it, display the results, *and* save the HTML report. diff --git a/Makefile b/Makefile index 9bcdebc2..3fcb765d 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,13 @@ test: FLASK_DEBUG= py.test tests examples tox-test: + pip install -r test-requirements.txt -q tox +cov: + pip install -r test-requirements.txt -q + FLASK_DEBUG= py.test --cov-report term --cov-report html --cov=flask --cov=examples tests examples + audit: python setup.py audit diff --git a/test-requirements.txt b/test-requirements.txt index e079f8a6..8be82dd0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,3 @@ +tox pytest +pytest-cov From 6e46d0cd3969f6c13ff61c95c81a975192232fed Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 13 Jun 2016 20:29:21 +0200 Subject: [PATCH 191/440] Fix PyPy3 support and add bug references Fix #1841 --- CHANGES | 7 +++++++ flask/_compat.py | 18 +++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index c057a675..49b42dba 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,13 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.11.2 +-------------- + +Bugfix release, unreleased + +- Fix crash when running under PyPy3, see pull request ``#1814``. + Version 0.11.1 -------------- diff --git a/flask/_compat.py b/flask/_compat.py index bfe607d6..071628fc 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -65,17 +65,25 @@ def with_metaclass(meta, *bases): # Certain versions of pypy have a bug where clearing the exception stack -# breaks the __exit__ function in a very peculiar way. This is currently -# true for pypy 2.2.1 for instance. The second level of exception blocks -# is necessary because pypy seems to forget to check if an exception -# happened until the next bytecode instruction? +# breaks the __exit__ function in a very peculiar way. The second level of +# exception blocks is necessary because pypy seems to forget to check if an +# exception happened until the next bytecode instruction? +# +# Relevant PyPy bugfix commit: +# https://bitbucket.org/pypy/pypy/commits/77ecf91c635a287e88e60d8ddb0f4e9df4003301 +# According to ronan on #pypy IRC, it is released in PyPy2 2.3 and later +# versions. +# +# Ubuntu 14.04 has PyPy 2.2.1, which does exhibit this bug. BROKEN_PYPY_CTXMGR_EXIT = False if hasattr(sys, 'pypy_version_info'): class _Mgr(object): def __enter__(self): return self def __exit__(self, *args): - sys.exc_clear() + if hasattr(sys, 'exc_clear'): + # Python 3 (PyPy3) doesn't have exc_clear + sys.exc_clear() try: try: with _Mgr(): From 5c12721730d18e2eddfe083cf1f6398af8915643 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 14 Jun 2016 22:45:24 +0200 Subject: [PATCH 192/440] Revert "Addressing Issue 1809" --- flask/app.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/flask/app.py b/flask/app.py index b1ea0464..cb81b0c6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -935,22 +935,7 @@ class Flask(_PackageBoundObject): @setupmethod def register_blueprint(self, blueprint, **options): - """Register a blueprint on the application. For information about - blueprints head over to :ref:`blueprints`. - - The blueprint name is passed in as the first argument. - Options are passed as additional keyword arguments and forwarded to - `blueprints` in an "options" dictionary. - - :param subdomain: set a subdomain for the blueprint - :param url_prefix: set the prefix for all URLs defined on the blueprint. - ``(url_prefix='/')`` - :param url_defaults: a dictionary with URL defaults that is added to - each and every URL defined with this blueprint - :param static_folder: add a static folder to urls in this blueprint - :param static_url_path: add a static url path to urls in this blueprint - :param template_folder: set an alternate template folder - :param root_path: set an alternate root path for this blueprint + """Registers a blueprint on the application. .. versionadded:: 0.7 """ From c0087204e5b83ad4e3983d07b66d17acdde0b9de Mon Sep 17 00:00:00 2001 From: Leo Tindall Date: Tue, 14 Jun 2016 23:55:47 -0700 Subject: [PATCH 193/440] Documentation: Clarify instructions about changing row_factory for SQLite3 (#1573) * Clarify instructions about changing row_factory When I was working through the tutorial, this was very confusing to me; so, I've added the code and clarification that would have helped me get through it faster. * Clarify the nature of Row objects * Rewrite code example for further clarity. --- docs/patterns/sqlite3.rst | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 1f9e3671..66a7c4c4 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -71,7 +71,8 @@ Now in each request handling function you can access `g.db` to get the current open database connection. To simplify working with SQLite, a row factory function is useful. It is executed for every result returned from the database to convert the result. For instance, in order to get -dictionaries instead of tuples, this could be inserted into ``get_db``:: +dictionaries instead of tuples, this could be inserted into the ``get_db`` +function we created above:: def make_dicts(cursor, row): return dict((cursor.description[idx][0], value) @@ -79,10 +80,26 @@ dictionaries instead of tuples, this could be inserted into ``get_db``:: db.row_factory = make_dicts -Or even simpler:: +This will make the sqlite3 module return dicts for this database connection, which are much nicer to deal with. Even more simply, we could place this in ``get_db`` instead:: db.row_factory = sqlite3.Row +This would use Row objects rather than dicts to return the results of queries. These are ``namedtuple`` s, so we can access them either by index or by key. For example, assuming we have a ``sqlite3.Row`` called ``r`` for the rows ``id``, ``FirstName``, ``LastName``, and ``MiddleInitial``:: + + >>> # You can get values based on the row's name + >>> r['FirstName'] + John + >>> # Or, you can get them based on index + >>> r[1] + John + # Row objects are also iterable: + >>> for value in r: + ... print(value) + 1 + John + Doe + M + Additionally, it is a good idea to provide a query function that combines getting the cursor, executing and fetching the results:: From b8aca21a392c367522eedb93f248f68005c84908 Mon Sep 17 00:00:00 2001 From: Archie Roller Date: Wed, 15 Jun 2016 21:27:06 +0500 Subject: [PATCH 194/440] Fix #1911 (#1913) --- flask/json.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/flask/json.py b/flask/json.py index b9ce4a08..16e0c295 100644 --- a/flask/json.py +++ b/flask/json.py @@ -19,10 +19,7 @@ from jinja2 import Markup # Use the same json implementation as itsdangerous on which we # depend anyways. -try: - from itsdangerous import simplejson as _json -except ImportError: - from itsdangerous import json as _json +from itsdangerous import json as _json # Figure out if simplejson escapes slashes. This behavior was changed From 9f2b3d815ec39279bfb838221580a36378a0de20 Mon Sep 17 00:00:00 2001 From: dcfix Date: Thu, 16 Jun 2016 14:40:23 -0600 Subject: [PATCH 195/440] Demonstrate how to add multiple urls to the same function endpoint #981 (#1900) * Demonstrate how to add multiple urls to the same function endpoint * Removed text as per untitaker, fixed spacing to be pep-8 compliant --- docs/patterns/lazyloading.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/patterns/lazyloading.rst b/docs/patterns/lazyloading.rst index 8388102a..acb77f94 100644 --- a/docs/patterns/lazyloading.rst +++ b/docs/patterns/lazyloading.rst @@ -90,14 +90,19 @@ Then you can define your central place to combine the views like this:: You can further optimize this in terms of amount of keystrokes needed to write this by having a function that calls into :meth:`~flask.Flask.add_url_rule` by prefixing a string with the project -name and a dot, and by wrapping `view_func` in a `LazyView` as needed:: +name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: - def url(url_rule, import_name, **options): + def url(import_name, url_rules=[], **options): view = LazyView('yourapplication.' + import_name) - app.add_url_rule(url_rule, view_func=view, **options) + for url_rule in url_rules: + app.add_url_rule(url_rule, view_func=view, **options) - url('/', 'views.index') - url('/user/', 'views.user') + # add a single route to the index view + url('views.index', ['/']) + + # add two routes to a single function endpoint + url_rules = ['/user/','/user/'] + url('views.user', url_rules) One thing to keep in mind is that before and after request handlers have to be in a file that is imported upfront to work properly on the first From 146cba53e7be1d433ecae9999ae662b48921e520 Mon Sep 17 00:00:00 2001 From: Baptiste Fontaine Date: Sun, 19 Jun 2016 22:27:23 +0200 Subject: [PATCH 196/440] wtforms: Add missing closing tags in example (#1917) --- docs/patterns/wtforms.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 6c08b808..8fc5e589 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -82,7 +82,7 @@ Here's an example :file:`_formhelpers.html` template with such a macro: .. sourcecode:: html+jinja {% macro render_field(field) %} -
{{ field.label }} +
{{ field.label }}
{{ field(**kwargs)|safe }} {% if field.errors %}
    @@ -108,7 +108,7 @@ takes advantage of the :file:`_formhelpers.html` template: .. sourcecode:: html+jinja {% from "_formhelpers.html" import render_field %} -
    +
    {{ render_field(form.username) }} {{ render_field(form.email) }} @@ -116,7 +116,7 @@ takes advantage of the :file:`_formhelpers.html` template: {{ render_field(form.confirm) }} {{ render_field(form.accept_tos) }}
    -

    +

    For more information about WTForms, head over to the `WTForms From 1ffd07ff5a0cd87020d7c527a4c83050f6ae9c14 Mon Sep 17 00:00:00 2001 From: Sudheer Satyanarayana Date: Tue, 21 Jun 2016 20:57:03 +0530 Subject: [PATCH 197/440] Fix small typo in python3 docs Add missing 'be' --- docs/python3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/python3.rst b/docs/python3.rst index 4d488f16..af61584c 100644 --- a/docs/python3.rst +++ b/docs/python3.rst @@ -23,8 +23,8 @@ specifically added for the Python 3 support and are quite new. Unless you require absolute compatibility, you should be fine with Python 3 nowadays. Most libraries and Flask extensions have been ported by now and using Flask with Python 3 is generally a smooth ride. However, keep in mind -that most libraries (including Werkzeug and Flask) might not quite as stable -on Python 3 yet. You might therefore sometimes run into bugs that are +that most libraries (including Werkzeug and Flask) might not be quite as +stable on Python 3 yet. You might therefore sometimes run into bugs that are usually encoding-related. The majority of the upgrade pain is in the lower-level libraries like From 663d786f6888bbd308528284b6a26d433ef314ee Mon Sep 17 00:00:00 2001 From: Ameya Pandilwar Date: Wed, 22 Jun 2016 16:26:35 -0400 Subject: [PATCH 198/440] Update documentation for upgrading Flask using 'pip' command (#1918) --- docs/upgrading.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 5ab73868..9e815379 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -14,10 +14,10 @@ This section of the documentation enumerates all the changes in Flask from release to release and how you can change your code to have a painless updating experience. -If you want to use the :command:`easy_install` command to upgrade your Flask -installation, make sure to pass it the :option:`-U` parameter:: +Use the :command:`pip` command to upgrade your existing Flask installation by +providing the :option:`--upgrade` parameter:: - $ easy_install -U Flask + $ pip install --upgrade Flask .. _upgrading-to-10: From d8c39f4b3753c0ebc93177661c43bfe35d7c019c Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 22 Jun 2016 14:53:27 -0700 Subject: [PATCH 199/440] Revert "wtforms: Add missing closing tags in example (#1917)" This reverts commit 146cba53e7be1d433ecae9999ae662b48921e520. --- docs/patterns/wtforms.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 8fc5e589..6c08b808 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -82,7 +82,7 @@ Here's an example :file:`_formhelpers.html` template with such a macro: .. sourcecode:: html+jinja {% macro render_field(field) %} -
    {{ field.label }}
    +
    {{ field.label }}
    {{ field(**kwargs)|safe }} {% if field.errors %}
      @@ -108,7 +108,7 @@ takes advantage of the :file:`_formhelpers.html` template: .. sourcecode:: html+jinja {% from "_formhelpers.html" import render_field %} -
      +
      {{ render_field(form.username) }} {{ render_field(form.email) }} @@ -116,7 +116,7 @@ takes advantage of the :file:`_formhelpers.html` template: {{ render_field(form.confirm) }} {{ render_field(form.accept_tos) }}
      -

      +

      For more information about WTForms, head over to the `WTForms From cd1a9b7d545a9566b6d8ab861572f2e80c67916e Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 22 Jun 2016 15:01:41 -0700 Subject: [PATCH 200/440] remove unnecessary form action attributes --- docs/patterns/fileuploads.rst | 8 ++++---- docs/patterns/flashing.rst | 2 +- docs/patterns/wtforms.rst | 2 +- docs/quickstart.rst | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 7a3cd28e..95c0032f 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -71,7 +71,7 @@ the file and redirects the user to the URL for the uploaded file:: Upload new File

      Upload new File

      -
      +

      @@ -104,9 +104,9 @@ before storing it directly on the filesystem. >>> secure_filename('../../../../home/username/.bashrc') 'home_username_.bashrc' -Now one last thing is missing: the serving of the uploaded files. In the -:func:`upload_file()` we redirect the user to -``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``. +Now one last thing is missing: the serving of the uploaded files. In the +:func:`upload_file()` we redirect the user to +``url_for('uploaded_file', filename=filename)``, that is, ``/uploads/filename``. So we write the :func:`uploaded_file` function to return the file of that name. As of Flask 0.5 we can use a function that does that for us:: diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index dc20a08c..7efd1446 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -78,7 +78,7 @@ And here is the :file:`login.html` template which also inherits from {% if error %}

      Error: {{ error }} {% endif %} -

      +
      Username:
      +
      {{ render_field(form.username) }} {{ render_field(form.email) }} diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 0d0028e2..ae1c7afa 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -784,7 +784,7 @@ sessions work:: session['username'] = request.form['username'] return redirect(url_for('index')) return ''' - +

      From 0e4607000d1e4a33a06c98578ec06226a43cdc56 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 23 Jun 2016 13:00:45 -0700 Subject: [PATCH 201/440] re-add passthrough_errors to cli runner (#1928) This got dropped during the cli simplification. Re: #1679 --- flask/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 9dfd339f..3796c083 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -423,7 +423,8 @@ def run_command(info, host, port, reload, debugger, eager_loading, print(' * Forcing debug mode %s' % (debug and 'on' or 'off')) run_simple(host, port, app, use_reloader=reload, - use_debugger=debugger, threaded=with_threads) + use_debugger=debugger, threaded=with_threads, + passthrough_errors=True) @click.command('shell', short_help='Runs a shell in the app context.') From 16471795117bb2d200888e49af5b621377bc1436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiss=20Gy=C3=B6rgy?= Date: Sun, 26 Apr 2015 09:40:20 +0200 Subject: [PATCH 202/440] Added routes command, which shows all the endpoints registered for the app. Orderable by rules, endpoints and methods. Shows up in the builtin command list. --- flask/cli.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/flask/cli.py b/flask/cli.py index 3796c083..2565657f 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -304,6 +304,7 @@ class FlaskGroup(AppGroup): if add_default_commands: self.add_command(run_command) self.add_command(shell_command) + self.add_command(routes_command) self._loaded_plugin_commands = False @@ -461,6 +462,33 @@ def shell_command(): code.interact(banner=banner, local=ctx) +@click.command('routes', short_help='Show routes for the app.') +@click.option('-r', 'order_by', flag_value='rule', default=True, help='Order by route') +@click.option('-e', 'order_by', flag_value='endpoint', help='Order by endpoint') +@click.option('-m', 'order_by', flag_value='methods', help='Order by methods') +@with_appcontext +def routes_command(order_by): + """Show all routes with endpoints and methods.""" + from flask.globals import _app_ctx_stack + app = _app_ctx_stack.top.app + + order_key = lambda rule: getattr(rule, order_by) + sorted_rules = sorted(app.url_map.iter_rules(), key=order_key) + + max_rule = max(len(rule.rule) for rule in sorted_rules) + max_ep = max(len(rule.endpoint) for rule in sorted_rules) + max_meth = max(len(', '.join(rule.methods)) for rule in sorted_rules) + + columnformat = '{:<%s} {:<%s} {:<%s}' % (max_rule, max_ep, max_meth) + click.echo(columnformat.format('Route', 'Endpoint', 'Methods')) + under_count = max_rule + max_ep + max_meth + 4 + click.echo('-' * under_count) + + for rule in sorted_rules: + methods = ', '.join(rule.methods) + click.echo(columnformat.format(rule.rule, rule.endpoint, methods)) + + cli = FlaskGroup(help="""\ This shell command acts as general utility script for Flask applications. From b8e826c16bbbada9128b70357c218ef79425499d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiss=20Gy=C3=B6rgy?= Date: Sat, 25 Jun 2016 13:17:33 +0200 Subject: [PATCH 203/440] Added tests, fixed some minor alignment problems. --- flask/cli.py | 3 +++ tests/test_apps/cliapp/routesapp.py | 18 +++++++++++++ tests/test_cli.py | 39 ++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/test_apps/cliapp/routesapp.py diff --git a/flask/cli.py b/flask/cli.py index 2565657f..e4992598 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -476,8 +476,11 @@ def routes_command(order_by): sorted_rules = sorted(app.url_map.iter_rules(), key=order_key) max_rule = max(len(rule.rule) for rule in sorted_rules) + max_rule = max(max_rule, len('Route')) max_ep = max(len(rule.endpoint) for rule in sorted_rules) + max_ep = max(max_ep, len('Endpoint')) max_meth = max(len(', '.join(rule.methods)) for rule in sorted_rules) + max_meth = max(max_meth, len('Methods')) columnformat = '{:<%s} {:<%s} {:<%s}' % (max_rule, max_ep, max_meth) click.echo(columnformat.format('Route', 'Endpoint', 'Methods')) diff --git a/tests/test_apps/cliapp/routesapp.py b/tests/test_apps/cliapp/routesapp.py new file mode 100644 index 00000000..84060546 --- /dev/null +++ b/tests/test_apps/cliapp/routesapp.py @@ -0,0 +1,18 @@ +from __future__ import absolute_import, print_function + +from flask import Flask + + +noroute_app = Flask('noroute app') +simpleroute_app = Flask('simpleroute app') +only_POST_route_app = Flask('GET route app') + + +@simpleroute_app.route('/simpleroute') +def simple(): + pass + + +@only_POST_route_app.route('/only-post', methods=['POST']) +def only_post(): + pass diff --git a/tests/test_cli.py b/tests/test_cli.py index 4a3d0831..7df00167 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -20,7 +20,7 @@ import pytest from click.testing import CliRunner from flask import Flask, current_app -from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ +from flask.cli import cli, AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \ find_default_import_path @@ -170,3 +170,40 @@ def test_flaskgroup(): result = runner.invoke(cli, ['test']) assert result.exit_code == 0 assert result.output == 'flaskgroup\n' + + +class TestRoutes: + def test_no_route(self, monkeypatch): + monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:noroute_app') + runner = CliRunner() + result = runner.invoke(cli, ['routes'], catch_exceptions=False) + assert result.exit_code == 0 + assert result.output == """\ +Route Endpoint Methods +----------------------------------------------------- +/static/ static HEAD, OPTIONS, GET +""" + + def test_simple_route(self, monkeypatch): + monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:simpleroute_app') + runner = CliRunner() + result = runner.invoke(cli, ['routes'], catch_exceptions=False) + assert result.exit_code == 0 + assert result.output == """\ +Route Endpoint Methods +----------------------------------------------------- +/simpleroute simple HEAD, OPTIONS, GET +/static/ static HEAD, OPTIONS, GET +""" + + def test_only_POST_route(self, monkeypatch): + monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:only_POST_route_app') + runner = CliRunner() + result = runner.invoke(cli, ['routes'], catch_exceptions=False) + assert result.exit_code == 0 + assert result.output == """\ +Route Endpoint Methods +------------------------------------------------------ +/only-post only_post POST, OPTIONS +/static/ static HEAD, OPTIONS, GET +""" From 1b764cff93696a75c6248b6f956426cbefa097bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kiss=20Gy=C3=B6rgy?= Date: Sat, 25 Jun 2016 13:24:43 +0200 Subject: [PATCH 204/440] Added runner fixture --- tests/test_cli.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 7df00167..db82ae8e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -25,6 +25,11 @@ from flask.cli import cli, AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ find_default_import_path +@pytest.fixture +def runner(): + return CliRunner() + + def test_cli_name(test_apps): """Make sure the CLI object's name is the app's name and not the app itself""" from cliapp.app import testapp @@ -108,7 +113,7 @@ def test_scriptinfo(test_apps): assert obj.load_app() == app -def test_with_appcontext(): +def test_with_appcontext(runner): """Test of with_appcontext.""" @click.command() @with_appcontext @@ -117,13 +122,12 @@ def test_with_appcontext(): obj = ScriptInfo(create_app=lambda info: Flask("testapp")) - runner = CliRunner() result = runner.invoke(testcmd, obj=obj) assert result.exit_code == 0 assert result.output == 'testapp\n' -def test_appgroup(): +def test_appgroup(runner): """Test of with_appcontext.""" @click.group(cls=AppGroup) def cli(): @@ -143,7 +147,6 @@ def test_appgroup(): obj = ScriptInfo(create_app=lambda info: Flask("testappgroup")) - runner = CliRunner() result = runner.invoke(cli, ['test'], obj=obj) assert result.exit_code == 0 assert result.output == 'testappgroup\n' @@ -153,7 +156,7 @@ def test_appgroup(): assert result.output == 'testappgroup\n' -def test_flaskgroup(): +def test_flaskgroup(runner): """Test FlaskGroup.""" def create_app(info): return Flask("flaskgroup") @@ -166,16 +169,14 @@ def test_flaskgroup(): def test(): click.echo(current_app.name) - runner = CliRunner() result = runner.invoke(cli, ['test']) assert result.exit_code == 0 assert result.output == 'flaskgroup\n' class TestRoutes: - def test_no_route(self, monkeypatch): + def test_no_route(self, runner, monkeypatch): monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:noroute_app') - runner = CliRunner() result = runner.invoke(cli, ['routes'], catch_exceptions=False) assert result.exit_code == 0 assert result.output == """\ @@ -184,9 +185,8 @@ Route Endpoint Methods /static/ static HEAD, OPTIONS, GET """ - def test_simple_route(self, monkeypatch): + def test_simple_route(self, runner, monkeypatch): monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:simpleroute_app') - runner = CliRunner() result = runner.invoke(cli, ['routes'], catch_exceptions=False) assert result.exit_code == 0 assert result.output == """\ @@ -196,9 +196,8 @@ Route Endpoint Methods /static/ static HEAD, OPTIONS, GET """ - def test_only_POST_route(self, monkeypatch): + def test_only_POST_route(self, runner, monkeypatch): monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:only_POST_route_app') - runner = CliRunner() result = runner.invoke(cli, ['routes'], catch_exceptions=False) assert result.exit_code == 0 assert result.output == """\ From 1928f28a6863a8bac27705950ce1327d661f4d2d Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 26 Jun 2016 13:03:29 -0700 Subject: [PATCH 205/440] clean up code formatting in some docs fix warnings while building docs --- docs/appcontext.rst | 6 +- docs/cli.rst | 12 ++-- docs/conf.py | 78 ++++++++++++----------- docs/installation.rst | 4 +- docs/patterns/distribute.rst | 26 ++++---- docs/quickstart.rst | 120 +++++++++++++++++------------------ docs/upgrading.rst | 18 +++--- flask/cli.py | 4 +- 8 files changed, 134 insertions(+), 134 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index 2ccacc8c..166c5aa3 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -74,9 +74,9 @@ The application context is also used by the :func:`~flask.url_for` function in case a ``SERVER_NAME`` was configured. This allows you to generate URLs even in the absence of a request. -If no request context has been pushed and an application context has -not been explicitly set, a ``RuntimeError`` will be raised. -:: +If no request context has been pushed and an application context has +not been explicitly set, a ``RuntimeError`` will be raised. :: + RuntimeError: Working outside of application context. Locality of the Context diff --git a/docs/cli.rst b/docs/cli.rst index 42596daf..10f5b34c 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -32,7 +32,7 @@ Python module that contains a Flask application. In that imported file the name of the app needs to be called ``app`` or optionally be specified after a colon. For instance -`mymodule:application` would tell it to use the `application` object in +``mymodule:application`` would tell it to use the `application` object in the :file:`mymodule.py` file. Given a :file:`hello.py` file with the application in it named ``app`` @@ -218,13 +218,13 @@ step. CLI Plugins ----------- -Flask extensions can always patch the `Flask.cli` instance with more +Flask extensions can always patch the :attr:`Flask.cli` instance with more commands if they want. However there is a second way to add CLI plugins -to Flask which is through `setuptools`. If you make a Python package that -should export a Flask command line plugin you can ship a `setup.py` file +to Flask which is through ``setuptools``. If you make a Python package that +should export a Flask command line plugin you can ship a :file:`setup.py` file that declares an entrypoint that points to a click command: -Example `setup.py`:: +Example :file:`setup.py`:: from setuptools import setup @@ -237,7 +237,7 @@ Example `setup.py`:: ''', ) -Inside `mypackage/commands.py` you can then export a Click object:: +Inside :file:`mypackage/commands.py` you can then export a Click object:: import click diff --git a/docs/conf.py b/docs/conf.py index 2f449da2..b37427a8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,13 +11,16 @@ # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function -import sys, os +from datetime import datetime +import os +import sys +import pkg_resources # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('_themes')) -sys.path.append(os.path.abspath('.')) +sys.path.append(os.path.join(os.path.dirname(__file__), '_themes')) +sys.path.append(os.path.dirname(__file__)) # -- General configuration ----------------------------------------------------- @@ -46,22 +49,21 @@ master_doc = 'index' # General information about the project. project = u'Flask' -copyright = u'2015, Armin Ronacher' +copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -import pkg_resources try: release = pkg_resources.get_distribution('Flask').version except pkg_resources.DistributionNotFound: print('Flask must be installed to build the documentation.') print('Install from source using `pip install -e .` in a virtualenv.') sys.exit(1) -del pkg_resources if 'dev' in release: - release = release.split('dev')[0] + 'dev' + release = ''.join(release.partition('dev')[:2]) + version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation @@ -100,14 +102,12 @@ exclude_patterns = ['_build'] # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'flask' +# html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = { - 'touch_icon': 'touch-icon.png' -} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] @@ -126,7 +126,7 @@ html_theme_path = ['_themes'] # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = "flask-favicon.ico" +html_favicon = '_static/flask-favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -143,9 +143,18 @@ html_static_path = ['_static'] # Custom sidebar templates, maps document names to template names. html_sidebars = { - 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], - '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', - 'sourcelink.html', 'searchbox.html'] + 'index': [ + 'sidebarintro.html', + 'sourcelink.html', + 'searchbox.html' + ], + '**': [ + 'sidebarlogo.html', + 'localtoc.html', + 'relations.html', + 'sourcelink.html', + 'searchbox.html' + ] } # Additional templates that should be rendered to pages, maps page names to @@ -187,8 +196,7 @@ htmlhelp_basename = 'Flaskdoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('latexindex', 'Flask.tex', u'Flask Documentation', - u'Armin Ronacher', 'manual'), + ('latexindex', 'Flask.tex', u'Flask Documentation', u'Armin Ronacher', 'manual'), ] # Documents to append as an appendix to all manuals. @@ -198,10 +206,10 @@ latex_documents = [ latex_use_modindex = False latex_elements = { - 'fontpkg': r'\usepackage{mathpazo}', - 'papersize': 'a4paper', - 'pointsize': '12pt', - 'preamble': r'\usepackage{flaskstyle}' + 'fontpkg': r'\usepackage{mathpazo}', + 'papersize': 'a4paper', + 'pointsize': '12pt', + 'preamble': r'\usepackage{flaskstyle}' } latex_use_parts = True @@ -245,21 +253,23 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf'] #epub_tocdepth = 3 intersphinx_mapping = { - 'https://docs.python.org/dev': None, - 'http://werkzeug.pocoo.org/docs/': None, - 'http://click.pocoo.org/': None, - 'http://jinja.pocoo.org/docs/': None, - 'http://www.sqlalchemy.org/docs/': None, - 'https://wtforms.readthedocs.io/en/latest/': None, - 'https://pythonhosted.org/blinker/': None + 'python': ('https://docs.python.org/3/', None), + 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None), + 'click': ('http://click.pocoo.org/', None), + 'jinja': ('http://jinja.pocoo.org/docs/', None), + 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None), + 'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None), + 'blinker': ('https://pythonhosted.org/blinker/', None) } -pygments_style = 'flask_theme_support.FlaskyStyle' - -# fall back if theme is not there try: __import__('flask_theme_support') -except ImportError as e: + pygments_style = 'flask_theme_support.FlaskyStyle' + html_theme = 'flask' + html_theme_options = { + 'touch_icon': 'touch-icon.png' + } +except ImportError: print('-' * 74) print('Warning: Flask themes unavailable. Building with default theme') print('If you want the Flask themes, run this command and build again:') @@ -267,10 +277,6 @@ except ImportError as e: print(' git submodule update --init') print('-' * 74) - pygments_style = 'tango' - html_theme = 'default' - html_theme_options = {} - # unwrap decorators def unwrap_decorators(): diff --git a/docs/installation.rst b/docs/installation.rst index 533a6fff..638d07ce 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -143,8 +143,8 @@ packages you will need are setuptools and pip - these will let you install anything else (like virtualenv). Fortunately there are two "bootstrap scripts" you can run to install either. -If you don't currently have either, then `get-pip.py` will install both for you -(you won't need to run ez_setup.py). +If you don't currently have either, then :file:`get-pip.py` will install both for you +(you won't need to run :file:`ez_setup.py`). `get-pip.py`_ diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index 8f79a2dd..72cc25d6 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -39,10 +39,8 @@ the process, also read the :ref:`fabric-deployment` chapter. Basic Setup Script ------------------ -Because you have Flask running, you have setuptools available on your system anyways. -Flask already depends upon setuptools. If you do not, fear not, there is a -script to install it for you: `ez_setup.py`_. Just download and -run with your Python interpreter. +Because you have Flask installed, you have setuptools available on your system. +Flask already depends upon setuptools. Standard disclaimer applies: :ref:`you better use a virtualenv `. @@ -67,7 +65,7 @@ A basic :file:`setup.py` file for a Flask application looks like this:: Please keep in mind that you have to list subpackages explicitly. If you want setuptools to lookup the packages for you automatically, you can use -the `find_packages` function:: +the ``find_packages`` function:: from setuptools import setup, find_packages @@ -76,12 +74,12 @@ the `find_packages` function:: packages=find_packages() ) -Most parameters to the `setup` function should be self explanatory, -`include_package_data` and `zip_safe` might not be. -`include_package_data` tells setuptools to look for a :file:`MANIFEST.in` file +Most parameters to the ``setup`` function should be self explanatory, +``include_package_data`` and ``zip_safe`` might not be. +``include_package_data`` tells setuptools to look for a :file:`MANIFEST.in` file and install all the entries that match as package data. We will use this to distribute the static files and templates along with the Python module -(see :ref:`distributing-resources`). The `zip_safe` flag can be used to +(see :ref:`distributing-resources`). The ``zip_safe`` flag can be used to force or prevent zip Archive creation. In general you probably don't want your packages to be installed as zip files because some tools do not support them and they make debugging a lot harder. @@ -123,13 +121,13 @@ your tarball:: Don't forget that even if you enlist them in your :file:`MANIFEST.in` file, they won't be installed for you unless you set the `include_package_data` -parameter of the `setup` function to ``True``! +parameter of the ``setup`` function to ``True``! Declaring Dependencies ---------------------- -Dependencies are declared in the `install_requires` parameter as a list. +Dependencies are declared in the ``install_requires`` parameter as a list. Each item in that list is the name of a package that should be pulled from PyPI on installation. By default it will always use the most recent version, but you can also provide minimum and maximum version @@ -159,20 +157,20 @@ Installing / Developing ----------------------- To install your application (ideally into a virtualenv) just run the -:file:`setup.py` script with the `install` parameter. It will install your +:file:`setup.py` script with the ``install`` parameter. It will install your application into the virtualenv's site-packages folder and also download and install all dependencies:: $ python setup.py install If you are developing on the package and also want the requirements to be -installed, you can use the `develop` command instead:: +installed, you can use the ``develop`` command instead:: $ python setup.py develop This has the advantage of just installing a link to the site-packages folder instead of copying the data over. You can then continue to work on -the code without having to run `install` again after each change. +the code without having to run ``install`` again after each change. .. _pip: https://pypi.python.org/pypi/pip diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ae1c7afa..433e4e08 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -26,7 +26,7 @@ So what did that code do? class will be our WSGI application. 2. Next we create an instance of this class. The first argument is the name of the application's module or package. If you are using a single module (as - in this example), you should use `__name__` because depending on if it's + in this example), you should use ``__name__`` because depending on if it's started as application or imported as module the name will be different (``'__main__'`` versus the actual import name). This is needed so that Flask knows where to look for templates, static files, and so on. For more @@ -42,17 +42,17 @@ your application :file:`flask.py` because this would conflict with Flask itself. To run the application you can either use the :command:`flask` command or -python's :option:`-m` switch with Flask. Before you can do that you need +python's ``-m`` switch with Flask. Before you can do that you need to tell your terminal the application to work with by exporting the -`FLASK_APP` environment variable:: +``FLASK_APP`` environment variable:: $ export FLASK_APP=hello.py $ flask run * Running on http://127.0.0.1:5000/ -If you are on Windows you need to use `set` instead of `export`. +If you are on Windows you need to use ``set`` instead of ``export``. -Alternatively you can use `python -m flask`:: +Alternatively you can use :command:`python -m flask`:: $ export FLASK_APP=hello.py $ python -m flask run @@ -86,7 +86,7 @@ should see your hello world greeting. What to do if the Server does not Start --------------------------------------- -In case the ``python -m flask`` fails or :command:`flask` does not exist, +In case the :command:`python -m flask` fails or :command:`flask` does not exist, there are multiple reasons this might be the case. First of all you need to look at the error message. @@ -95,14 +95,14 @@ Old Version of Flask Versions of Flask older than 0.11 use to have different ways to start the application. In short, the :command:`flask` command did not exist, and -neither did ``python -m flask``. In that case you have two options: +neither did :command:`python -m flask`. In that case you have two options: either upgrade to newer Flask versions or have a look at the :ref:`server` docs to see the alternative method for running a server. Invalid Import Name ``````````````````` -The :option:`-a` argument to :command:`flask` is the name of the module to +The ``-a`` argument to :command:`flask` is the name of the module to import. In case that module is incorrectly named you will get an import error upon start (or if debug is enabled when you navigate to the application). It will tell you what it tried to import and why it failed. @@ -123,13 +123,13 @@ That is not very nice and Flask can do better. If you enable debug support the server will reload itself on code changes, and it will also provide you with a helpful debugger if things go wrong. -To enable debug mode you can export the `FLASK_DEBUG` environment variable +To enable debug mode you can export the ``FLASK_DEBUG`` environment variable before running the server:: $ export FLASK_DEBUG=1 $ flask run -(On Windows you need to use `set` instead of `export`). +(On Windows you need to use ``set`` instead of ``export``). This does the following things: @@ -202,7 +202,7 @@ The following converters exist: =========== =============================================== `string` accepts any text without a slash (the default) `int` accepts integers -`float` like `int` but for floating point values +`float` like ``int`` but for floating point values `path` like the default but also accepts slashes `any` matches one of the items provided `uuid` accepts UUID strings @@ -226,7 +226,7 @@ The following converters exist: Though they look rather similar, they differ in their use of the trailing slash in the URL *definition*. In the first case, the canonical URL for the - `projects` endpoint has a trailing slash. In that sense, it is similar to + ``projects`` endpoint has a trailing slash. In that sense, it is similar to a folder on a filesystem. Accessing it without a trailing slash will cause Flask to redirect to the canonical URL with the trailing slash. @@ -250,29 +250,29 @@ build a URL to a specific function you can use the :func:`~flask.url_for` function. It accepts the name of the function as first argument and a number of keyword arguments, each corresponding to the variable part of the URL rule. Unknown variable parts are appended to the URL as query parameters. Here are -some examples: - ->>> from flask import Flask, url_for ->>> app = Flask(__name__) ->>> @app.route('/') -... def index(): pass -... ->>> @app.route('/login') -... def login(): pass -... ->>> @app.route('/user/') -... def profile(username): pass -... ->>> with app.test_request_context(): -... print url_for('index') -... print url_for('login') -... print url_for('login', next='/') -... print url_for('profile', username='John Doe') -... -/ -/login -/login?next=/ -/user/John%20Doe +some examples:: + + >>> from flask import Flask, url_for + >>> app = Flask(__name__) + >>> @app.route('/') + ... def index(): pass + ... + >>> @app.route('/login') + ... def login(): pass + ... + >>> @app.route('/user/') + ... def profile(username): pass + ... + >>> with app.test_request_context(): + ... print url_for('index') + ... print url_for('login') + ... print url_for('login', next='/') + ... print url_for('profile', username='John Doe') + ... + / + /login + /login?next=/ + /user/John%20Doe (This also uses the :meth:`~flask.Flask.test_request_context` method, explained below. It tells Flask to behave as though it is handling a request, even @@ -288,8 +288,8 @@ There are three good reasons for this: remember to change URLs all over the place. 2. URL building will handle escaping of special characters and Unicode data transparently for you, so you don't have to deal with them. -3. If your application is placed outside the URL root (say, in - ``/myapplication`` instead of ``/``), :func:`~flask.url_for` will handle +3. If your application is placed outside the URL root - say, in + ``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle that properly for you. @@ -298,7 +298,7 @@ HTTP Methods HTTP (the protocol web applications are speaking) knows different methods for accessing URLs. By default, a route only answers to ``GET`` requests, but that -can be changed by providing the `methods` argument to the +can be changed by providing the ``methods`` argument to the :meth:`~flask.Flask.route` decorator. Here are some examples:: from flask import request @@ -446,22 +446,22 @@ know how that works, head over to the :ref:`template-inheritance` pattern documentation. Basically template inheritance makes it possible to keep certain elements on each page (like header, navigation and footer). -Automatic escaping is enabled, so if `name` contains HTML it will be escaped +Automatic escaping is enabled, so if ``name`` contains HTML it will be escaped automatically. If you can trust a variable and you know that it will be safe HTML (for example because it came from a module that converts wiki markup to HTML) you can mark it as safe by using the :class:`~jinja2.Markup` class or by using the ``|safe`` filter in the template. Head over to the Jinja 2 documentation for more examples. -Here is a basic introduction to how the :class:`~jinja2.Markup` class works: +Here is a basic introduction to how the :class:`~jinja2.Markup` class works:: ->>> from flask import Markup ->>> Markup('Hello %s!') % 'hacker' -Markup(u'Hello <blink>hacker</blink>!') ->>> Markup.escape('hacker') -Markup(u'<blink>hacker</blink>') ->>> Markup('Marked up » HTML').striptags() -u'Marked up \xbb HTML' + >>> from flask import Markup + >>> Markup('Hello %s!') % 'hacker' + Markup(u'Hello <blink>hacker</blink>!') + >>> Markup.escape('hacker') + Markup(u'<blink>hacker</blink>') + >>> Markup('Marked up » HTML').striptags() + u'Marked up \xbb HTML' .. versionchanged:: 0.5 @@ -540,7 +540,7 @@ The Request Object The request object is documented in the API section and we will not cover it here in detail (see :class:`~flask.request`). Here is a broad overview of some of the most common operations. First of all you have to import it from -the `flask` module:: +the ``flask`` module:: from flask import request @@ -563,7 +563,7 @@ attributes mentioned above:: # was GET or the credentials were invalid return render_template('login.html', error=error) -What happens if the key does not exist in the `form` attribute? In that +What happens if the key does not exist in the ``form`` attribute? In that case a special :exc:`KeyError` is raised. You can catch it like a standard :exc:`KeyError` but if you don't do that, a HTTP 400 Bad Request error page is shown instead. So for many situations you don't have to @@ -725,17 +725,15 @@ converting return values into response objects is as follows: 3. If a tuple is returned the items in the tuple can provide extra information. Such tuples have to be in the form ``(response, status, headers)`` or ``(response, headers)`` where at least one item has - to be in the tuple. The `status` value will override the status code - and `headers` can be a list or dictionary of additional header values. + to be in the tuple. The ``status`` value will override the status code + and ``headers`` can be a list or dictionary of additional header values. 4. If none of that works, Flask will assume the return value is a valid WSGI application and convert that into a response object. If you want to get hold of the resulting response object inside the view you can use the :func:`~flask.make_response` function. -Imagine you have a view like this: - -.. sourcecode:: python +Imagine you have a view like this:: @app.errorhandler(404) def not_found(error): @@ -743,9 +741,7 @@ Imagine you have a view like this: You just need to wrap the return expression with :func:`~flask.make_response` and get the response object to modify it, then -return it: - -.. sourcecode:: python +return it:: @app.errorhandler(404) def not_found(error): @@ -807,13 +803,13 @@ not using the template engine (as in this example). The problem with random is that it's hard to judge what is truly random. And a secret key should be as random as possible. Your operating system has ways to generate pretty random stuff based on a cryptographic - random generator which can be used to get such a key: + random generator which can be used to get such a key:: - >>> import os - >>> os.urandom(24) - '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O>> import os + >>> os.urandom(24) + '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O Date: Sun, 26 Jun 2016 22:32:47 +0200 Subject: [PATCH 206/440] fix docs: name of url_value_preprocessor method (#1932) This typo got introduced in 5da1fc22153032923b1560a34a0f346d6517a12d, the original commit for the url_value_preprocessor decorator. --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index cb81b0c6..dac7fe26 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1804,7 +1804,7 @@ class Flask(_PackageBoundObject): if it was the return value from the view and further request handling is stopped. - This also triggers the :meth:`url_value_processor` functions before + This also triggers the :meth:`url_value_preprocessor` functions before the actual :meth:`before_request` functions are called. """ bp = _request_ctx_stack.top.request.blueprint From 516ce59f95a3b5d2fffbcde8abfdf1951e748361 Mon Sep 17 00:00:00 2001 From: Antoine Catton Date: Tue, 28 Jun 2016 17:20:25 +0200 Subject: [PATCH 207/440] Add the ability to combine MethodViews --- flask/views.py | 6 +++++- tests/test_views.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 6e249180..922bb132 100644 --- a/flask/views.py +++ b/flask/views.py @@ -102,12 +102,16 @@ class View(object): return view +def get_methods(cls): + return getattr(cls, 'methods', []) or [] + + class MethodViewType(type): def __new__(cls, name, bases, d): rv = type.__new__(cls, name, bases, d) if 'methods' not in d: - methods = set(rv.methods or []) + methods = set(m for b in bases for m in get_methods(b)) for key in d: if key in http_method_funcs: methods.add(key.upper()) diff --git a/tests/test_views.py b/tests/test_views.py index 8a66bd53..65981dbd 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -160,3 +160,45 @@ def test_endpoint_override(): # But these tests should still pass. We just log a warning. common_test(app) + +def test_multiple_inheritance(): + app = flask.Flask(__name__) + + class GetView(flask.views.MethodView): + def get(self): + return 'GET' + + class DeleteView(flask.views.MethodView): + def delete(self): + return 'DELETE' + + class GetDeleteView(GetView, DeleteView): + pass + + app.add_url_rule('/', view_func=GetDeleteView.as_view('index')) + + c = app.test_client() + assert c.get('/').data == b'GET' + assert c.delete('/').data == b'DELETE' + assert sorted(GetDeleteView.methods) == ['DELETE', 'GET'] + +def test_remove_method_from_parent(): + app = flask.Flask(__name__) + + class GetView(flask.views.MethodView): + def get(self): + return 'GET' + + class OtherView(flask.views.MethodView): + def post(self): + return 'POST' + + class View(GetView, OtherView): + methods = ['GET'] + + app.add_url_rule('/', view_func=View.as_view('index')) + + c = app.test_client() + assert c.get('/').data == b'GET' + assert c.post('/').status_code == 405 + assert sorted(View.methods) == ['GET'] From 0c459762ea15960886fd0c2960382212c280eee3 Mon Sep 17 00:00:00 2001 From: John Still Date: Sat, 2 Jul 2016 17:03:36 -0500 Subject: [PATCH 208/440] clarify blueprint 404 error handling in docs --- docs/blueprints.rst | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 89d3701e..98a3d630 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -177,11 +177,11 @@ the `template_folder` parameter to the :class:`Blueprint` constructor:: admin = Blueprint('admin', __name__, template_folder='templates') For static files, the path can be absolute or relative to the blueprint -resource folder. +resource folder. -The template folder is added to the search path of templates but with a lower -priority than the actual application's template folder. That way you can -easily override templates that a blueprint provides in the actual application. +The template folder is added to the search path of templates but with a lower +priority than the actual application's template folder. That way you can +easily override templates that a blueprint provides in the actual application. This also means that if you don't want a blueprint template to be accidentally overridden, make sure that no other blueprint or actual application template has the same relative path. When multiple blueprints provide the same relative @@ -194,7 +194,7 @@ want to render the template ``'admin/index.html'`` and you have provided this: :file:`yourapplication/admin/templates/admin/index.html`. The reason for the extra ``admin`` folder is to avoid getting our template overridden by a template named ``index.html`` in the actual application template -folder. +folder. To further reiterate this: if you have a blueprint named ``admin`` and you want to render a template called :file:`index.html` which is specific to this @@ -245,4 +245,22 @@ Here is an example for a "404 Page Not Found" exception:: def page_not_found(e): return render_template('pages/404.html') +Most errorhandlers will simply work as expected; however, there is a caveat +concerning handlers for 404 and 405 exceptions. These errorhandlers are only +invoked from an appropriate ``raise`` statement or a call to ``abort`` in another +of the blueprint's view functions; they are not invoked by, e.g., an invalid URL +access. This is because the blueprint does not "own" a certain URL space, so +the application instance has no way of knowing which blueprint errorhandler it +should run if given an invalid URL. If you would like to execute different +handling strategies for these errors based on URL prefixes, they may be defined +at the application level using the ``request`` proxy object:: + + @app.errorhandler(404) + @app.errorhandler(405) + def _handle_api_error(ex): + if request.path.startswith('/api/'): + return jsonify_error(ex) + else: + return ex + More information on error handling see :ref:`errorpages`. From b7a0cc61c54dbfebf8c3b21634ec9e37596b1e9a Mon Sep 17 00:00:00 2001 From: Olexander Yermakov Date: Tue, 5 Jul 2016 22:00:43 +0300 Subject: [PATCH 209/440] Update installation documentation for using 'pip' command (#1920) --- docs/installation.rst | 47 +++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 638d07ce..91d95270 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -40,24 +40,20 @@ installations of Python, one for each project. It doesn't actually install separate copies of Python, but it does provide a clever way to keep different project environments isolated. Let's see how virtualenv works. -If you are on Mac OS X or Linux, chances are that one of the following two -commands will work for you:: - - $ sudo easy_install virtualenv - -or even better:: +If you are on Mac OS X or Linux, chances are that the following +command will work for you:: $ sudo pip install virtualenv -One of these will probably install virtualenv on your system. Maybe it's even +It will probably install virtualenv on your system. Maybe it's even in your package manager. If you use Ubuntu, try:: $ sudo apt-get install python-virtualenv -If you are on Windows and don't have the :command:`easy_install` command, you must +If you are on Windows and don't have the ``easy_install`` command, you must install it first. Check the :ref:`windows-easy-install` section for more information about how to do that. Once you have it installed, run the same -commands as above, but without the :command:`sudo` prefix. +commands as above, but without the ``sudo`` prefix. Once you have virtualenv installed, just fire up a shell and create your own environment. I usually create a project folder and a :file:`venv` @@ -99,19 +95,19 @@ System-Wide Installation ------------------------ This is possible as well, though I do not recommend it. Just run -:command:`pip` with root privileges:: +``pip`` with root privileges:: $ sudo pip install Flask (On Windows systems, run it in a command-prompt window with administrator -privileges, and leave out :command:`sudo`.) +privileges, and leave out ``sudo``.) Living on the Edge ------------------ If you want to work with the latest version of Flask, there are two ways: you -can either let :command:`pip` pull in the development version, or you can tell +can either let ``pip`` pull in the development version, or you can tell it to operate on a git checkout. Either way, virtualenv is recommended. Get the git checkout in a new virtualenv and run in development mode:: @@ -131,40 +127,34 @@ This will pull in the dependencies and activate the git head as the current version inside the virtualenv. Then all you have to do is run ``git pull origin`` to update to the latest version. - .. _windows-easy-install: `pip` and `setuptools` on Windows --------------------------------- -Sometimes getting the standard "Python packaging tools" like *pip*, *setuptools* -and *virtualenv* can be a little trickier, but nothing very hard. The two crucial -packages you will need are setuptools and pip - these will let you install -anything else (like virtualenv). Fortunately there are two "bootstrap scripts" -you can run to install either. +Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools`` +and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial +package you will need is pip - this will let you install +anything else (like virtualenv). Fortunately there is a "bootstrap script" +you can run to install. -If you don't currently have either, then :file:`get-pip.py` will install both for you -(you won't need to run :file:`ez_setup.py`). +If you don't currently have ``pip``, then `get-pip.py` will install it for you. `get-pip.py`_ -To install the latest setuptools, you can use its bootstrap file: - -`ez_setup.py`_ - -Either should be double-clickable once you download them. If you already have pip, +It should be double-clickable once you download it. If you already have ``pip``, you can upgrade them by running:: > pip install --upgrade pip setuptools -Most often, once you pull up a command prompt you want to be able to type :command:`pip` -and :command:`python` which will run those things, but this might not automatically happen +Most often, once you pull up a command prompt you want to be able to type ``pip`` +and ``python`` which will run those things, but this might not automatically happen on Windows, because it doesn't know where those executables are (give either a try!). To fix this, you should be able to navigate to your Python install directory (e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the :file:`win_add2path.py` file and run that. Open a **new** Command Prompt and -check that you can now just type :command:`python` to bring up the interpreter. +check that you can now just type ``python`` to bring up the interpreter. Finally, to install `virtualenv`_, you can simply run:: @@ -173,4 +163,3 @@ Finally, to install `virtualenv`_, you can simply run:: Then you can be off on your way following the installation instructions above. .. _get-pip.py: https://bootstrap.pypa.io/get-pip.py -.. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py From f19d3bd67e0d8213013cf06f47d951c8735515c8 Mon Sep 17 00:00:00 2001 From: Hyunchel Kim Date: Tue, 5 Jul 2016 14:46:01 -0500 Subject: [PATCH 210/440] Enhance tests.test_cli.test_find_best_app (#1882) This commit adds a test case for `test_find_best_app` where Module object does not contain Flask application. Also cleans the function little bit to provides more meaningful comment. --- tests/test_cli.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 4a3d0831..3f2cceab 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -32,24 +32,27 @@ def test_cli_name(test_apps): def test_find_best_app(test_apps): - """Test of find_best_app.""" - class mod: + """Test if `find_best_app` behaves as expected with different combinations of input.""" + class Module: app = Flask('appname') - assert find_best_app(mod) == mod.app + assert find_best_app(Module) == Module.app - class mod: + class Module: application = Flask('appname') - assert find_best_app(mod) == mod.application + assert find_best_app(Module) == Module.application - class mod: + class Module: myapp = Flask('appname') - assert find_best_app(mod) == mod.myapp + assert find_best_app(Module) == Module.myapp - class mod: - myapp = Flask('appname') - myapp2 = Flask('appname2') + class Module: + pass + pytest.raises(NoAppException, find_best_app, Module) - pytest.raises(NoAppException, find_best_app, mod) + class Module: + myapp1 = Flask('appname1') + myapp2 = Flask('appname2') + pytest.raises(NoAppException, find_best_app, Module) def test_prepare_exec_for_file(test_apps): From 17d4cb3828b9520be6c4e64e6b7548a561aa6e7a Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Tue, 5 Jul 2016 20:30:59 -0400 Subject: [PATCH 211/440] Address #1902: Converts example/flaskr to have a setup.py (#1945) * Converts example/flaskr to have a setup.py Makes the flaskr app easier to run, ex. workflow: - pip install --editable . - export FLASK_APP=flaskr.flaskr - flask initdb - flask run Testing is also easier now: - python setup.py test * Fixed an import error in flaskr/tests - the statement `import flaskr` caused errors in python3 - `from . import flaskr` fixes the issue in 2.7.11 and 3.5.1 * Better project structure and updates the docs - Re-factors *flaskr*'s project structure a bit - Updates docs to make sense with the new structure - Adds a new step about installing Flask apps with setuptools - Switches first-person style writing to second-person (reads better IMO) - Adds segments in *testing.rst* for running tests with setuptools * Remove __init__.py from tests - py.test recommends not using __init__.py * Fix testing import errors --- docs/tutorial/css.rst | 4 +- docs/tutorial/dbcon.rst | 28 +++--- docs/tutorial/dbinit.rst | 29 +++--- docs/tutorial/folders.rst | 26 +++--- docs/tutorial/index.rst | 1 + docs/tutorial/introduction.rst | 9 +- docs/tutorial/schema.rst | 10 +-- docs/tutorial/setup.rst | 73 ++++++--------- docs/tutorial/setuptools.rst | 89 +++++++++++++++++++ docs/tutorial/templates.rst | 15 ++-- docs/tutorial/testing.rst | 78 ++++++++++++++++ docs/tutorial/views.rst | 43 +++++---- examples/flaskr/.gitignore | 1 + examples/flaskr/MANIFEST.in | 3 + examples/flaskr/README | 14 +-- examples/flaskr/flaskr/__init__.py | 0 examples/flaskr/{ => flaskr}/flaskr.py | 0 examples/flaskr/{ => flaskr}/schema.sql | 0 examples/flaskr/{ => flaskr}/static/style.css | 0 .../flaskr/{ => flaskr}/templates/layout.html | 0 .../flaskr/{ => flaskr}/templates/login.html | 0 .../{ => flaskr}/templates/show_entries.html | 0 examples/flaskr/setup.cfg | 2 + examples/flaskr/setup.py | 16 ++++ examples/flaskr/tests/context.py | 6 ++ examples/flaskr/{ => tests}/test_flaskr.py | 3 +- 26 files changed, 323 insertions(+), 127 deletions(-) create mode 100644 docs/tutorial/setuptools.rst create mode 100644 examples/flaskr/MANIFEST.in create mode 100644 examples/flaskr/flaskr/__init__.py rename examples/flaskr/{ => flaskr}/flaskr.py (100%) rename examples/flaskr/{ => flaskr}/schema.sql (100%) rename examples/flaskr/{ => flaskr}/static/style.css (100%) rename examples/flaskr/{ => flaskr}/templates/layout.html (100%) rename examples/flaskr/{ => flaskr}/templates/login.html (100%) rename examples/flaskr/{ => flaskr}/templates/show_entries.html (100%) create mode 100644 examples/flaskr/setup.cfg create mode 100644 examples/flaskr/setup.py create mode 100644 examples/flaskr/tests/context.py rename examples/flaskr/{ => tests}/test_flaskr.py (98%) diff --git a/docs/tutorial/css.rst b/docs/tutorial/css.rst index 3f0e932a..ea461a89 100644 --- a/docs/tutorial/css.rst +++ b/docs/tutorial/css.rst @@ -1,11 +1,11 @@ .. _tutorial-css: -Step 7: Adding Style +Step 9: Adding Style ==================== Now that everything else works, it's time to add some style to the application. Just create a stylesheet called :file:`style.css` in the -:file:`static` folder we created before: +:file:`static` folder: .. sourcecode:: css diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 4b5b0915..9f4428b9 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -1,25 +1,23 @@ .. _tutorial-dbcon: -Step 3: Database Connections +Step 4: Database Connections ---------------------------- -We have created a function for establishing a database connection with -`connect_db`, but by itself, that's not particularly useful. Creating and -closing database connections all the time is very inefficient, so we want -to keep it around for longer. Because database connections encapsulate a -transaction, we also need to make sure that only one request at the time -uses the connection. How can we elegantly do that with Flask? +You now have a function for establishing a database connection with +`connect_db`, but by itself, it is not particularly useful. Creating and +closing database connections all the time is very inefficient, so you will +need to keep it around for longer. Because database connections +encapsulate a transaction, you will need to make sure that only one +request at a time uses the connection. An elegant way to do this is by +utilizing the *application context*. -This is where the application context comes into play, so let's start -there. - -Flask provides us with two contexts: the application context and the -request context. For the time being, all you have to know is that there +Flask provides two contexts: the *application context* and the +*request context*. For the time being, all you have to know is that there are special variables that use these. For instance, the :data:`~flask.request` variable is the request object associated with the current request, whereas :data:`~flask.g` is a general purpose -variable associated with the current application context. We will go into -the details of this a bit later. +variable associated with the current application context. The tutorial +will cover some more details of this later on. For the time being, all you have to know is that you can store information safely on the :data:`~flask.g` object. @@ -37,7 +35,7 @@ already established connection:: g.sqlite_db = connect_db() return g.sqlite_db -So now we know how to connect, but how do we properly disconnect? For +Now you know how to connect, but how can you properly disconnect? For that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext` decorator. It's executed every time the application context tears down:: diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 2c26dd1a..09997906 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -1,6 +1,6 @@ .. _tutorial-dbinit: -Step 4: Creating The Database +Step 5: Creating The Database ============================= As outlined earlier, Flaskr is a database powered application, and more @@ -16,13 +16,14 @@ Such a schema can be created by piping the ``schema.sql`` file into the The downside of this is that it requires the ``sqlite3`` command to be installed, which is not necessarily the case on every system. This also -requires that we provide the path to the database, which can introduce +requires that you provide the path to the database, which can introduce errors. It's a good idea to add a function that initializes the database -for you to the application. +for you, to the application. -To do this, we can create a function and hook it into the :command:`flask` -command that initializes the database. Let me show you the code first. Just -add this function below the `connect_db` function in :file:`flaskr.py`:: +To do this, you can create a function and hook it into a :command:`flask` +command that initializes the database. For now just take a look at the +code segment below. A good place to add this function, and command, is +just below the `connect_db` function in :file:`flaskr.py`:: def init_db(): db = get_db() @@ -38,23 +39,23 @@ add this function below the `connect_db` function in :file:`flaskr.py`:: The ``app.cli.command()`` decorator registers a new command with the :command:`flask` script. When the command executes, Flask will automatically -create an application context for us bound to the right application. -Within the function, we can then access :attr:`flask.g` and other things as -we would expect. When the script ends, the application context tears down +create an application context which is bound to the right application. +Within the function, you can then access :attr:`flask.g` and other things as +you might expect. When the script ends, the application context tears down and the database connection is released. -We want to keep an actual function around that initializes the database, +You will want to keep an actual function around that initializes the database, though, so that we can easily create databases in unit tests later on. (For more information see :ref:`testing`.) The :func:`~flask.Flask.open_resource` method of the application object is a convenient helper function that will open a resource that the application provides. This function opens a file from the resource -location (your ``flaskr`` folder) and allows you to read from it. We are -using this here to execute a script on the database connection. +location (the :file:`flaskr/flaskr` folder) and allows you to read from it. +It is used in this example to execute a script on the database connection. -The connection object provided by SQLite can give us a cursor object. -On that cursor, there is a method to execute a complete script. Finally, we +The connection object provided by SQLite can give you a cursor object. +On that cursor, there is a method to execute a complete script. Finally, you only have to commit the changes. SQLite3 and other transactional databases will not commit unless you explicitly tell it to. diff --git a/docs/tutorial/folders.rst b/docs/tutorial/folders.rst index fba19d72..4e117d1f 100644 --- a/docs/tutorial/folders.rst +++ b/docs/tutorial/folders.rst @@ -3,21 +3,25 @@ Step 0: Creating The Folders ============================ -Before we get started, let's create the folders needed for this +Before getting started, you will need to create the folders needed for this application:: /flaskr - /static - /templates + /flaskr + /static + /templates -The ``flaskr`` folder is not a Python package, but just something where we -drop our files. Later on, we will put our database schema as well as main -module into this folder. It is done in the following way. The files inside -the :file:`static` folder are available to users of the application via HTTP. -This is the place where CSS and Javascript files go. Inside the -:file:`templates` folder, Flask will look for `Jinja2`_ templates. The -templates you create later on in the tutorial will go in this directory. +The application will be installed and run as Python package. This is the +recommended way to install and run Flask applications. You will see exactly +how to run ``flaskr`` later on in this tutorial. For now go ahead and create +the applications directory structure. In the next few steps you will be +creating the database schema as well as the main module. -Continue with :ref:`tutorial-schema`. +As a quick side note, the files inside of the :file:`static` folder are +available to users of the application via HTTP. This is the place where CSS and +Javascript files go. Inside the :file:`templates` folder, Flask will look for +`Jinja2`_ templates. You will see examples of this later on. + +For now you should continue with :ref:`tutorial-schema`. .. _Jinja2: http://jinja.pocoo.org/ diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 80b9fc28..ccd4e7d2 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -24,6 +24,7 @@ the `example source`_. folders schema setup + setuptools dbcon dbinit views diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index e3da4b97..dd46628b 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -3,8 +3,9 @@ Introducing Flaskr ================== -We will call our blogging application Flaskr, but feel free to choose your own -less Web-2.0-ish name ;) Essentially, we want it to do the following things: +This tutorial will demonstrate a blogging application named Flaskr, but feel +free to choose your own less Web-2.0-ish name ;) Essentially, it will do the +following things: 1. Let the user sign in and out with credentials specified in the configuration. Only one user is supported. @@ -14,8 +15,8 @@ less Web-2.0-ish name ;) Essentially, we want it to do the following things: 3. The index page shows all entries so far in reverse chronological order (newest on top) and the user can add new ones from there if logged in. -We will be using SQLite3 directly for this application because it's good -enough for an application of this size. For larger applications, however, +SQLite3 will be used directly for this application because it's good enough +for an application of this size. For larger applications, however, it makes a lot of sense to use `SQLAlchemy`_, as it handles database connections in a more intelligent way, allowing you to target different relational databases at once and more. You might also want to consider diff --git a/docs/tutorial/schema.rst b/docs/tutorial/schema.rst index b164b94c..00f56f09 100644 --- a/docs/tutorial/schema.rst +++ b/docs/tutorial/schema.rst @@ -3,10 +3,10 @@ Step 1: Database Schema ======================= -First, we want to create the database schema. Only a single table is needed -for this application and we only want to support SQLite, so creating the -database schema is quite easy. Just put the following contents into a file -named `schema.sql` in the just created `flaskr` folder: +In this step, you will create the database schema. Only a single table is +needed for this application and it will only support SQLite. All you need to do +is put the following contents into a file named :file:`schema.sql` in the +:file:`flaskr/flaskr` folder: .. sourcecode:: sql @@ -17,7 +17,7 @@ named `schema.sql` in the just created `flaskr` folder: 'text' text not null ); -This schema consists of a single table called ``entries``. Each row in +This schema consists of a single table called ``entries``. Each row in this table has an ``id``, a ``title``, and a ``text``. The ``id`` is an automatically incrementing integer and a primary key, the other two are strings that must not be null. diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index fef71722..78b6390a 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -3,15 +3,16 @@ Step 2: Application Setup Code ============================== -Now that we have the schema in place, we can create the application module. -Let's call it ``flaskr.py``. We will place this file inside the ``flaskr`` -folder. We will begin by adding the imports we need and by adding the config -section. For small applications, it is possible to drop the configuration -directly into the module, and this is what we will be doing here. However, -a cleaner solution would be to create a separate ``.ini`` or ``.py`` file, -load that, and import the values from there. +Now that the schema is in place, you can create the application module, +:file:`flaskr.py`. This file should be placed inside of the +:file:`flaskr/flaskr` folder. The first several lines of code in the +application module are the needed import statements. After that there will be a +few lines of configuration code. For small applications like ``flaskr``, it is +possible to drop the configuration directly into the module. However, a cleaner +solution is to create a separate ``.ini`` or ``.py`` file, load that, and +import the values from there. -First, we add the imports in :file:`flaskr.py`:: +Here are the import statements (in :file:`flaskr.py`):: # all the imports import os @@ -19,12 +20,13 @@ First, we add the imports in :file:`flaskr.py`:: from flask import Flask, request, session, g, redirect, url_for, abort, \ render_template, flash -Next, we can create our actual application and initialize it with the -config from the same file in :file:`flaskr.py`:: +The next couple lines will create the actual application instance and +initialize it with the config from the same file in :file:`flaskr.py`: - # create our little application :) - app = Flask(__name__) - app.config.from_object(__name__) +.. sourcecode:: python + + app = Flask(__name__) # create the application instance :) + app.config.from_object(__name__) # load config from this file , flaskr.py # Load default config and override config from an environment variable app.config.update(dict( @@ -35,8 +37,8 @@ config from the same file in :file:`flaskr.py`:: )) app.config.from_envvar('FLASKR_SETTINGS', silent=True) -The :class:`~flask.Config` object works similarly to a dictionary so we -can update it with new values. +The :class:`~flask.Config` object works similarly to a dictionary, so it can be +updated with new values. .. admonition:: Database Path @@ -55,10 +57,10 @@ can update it with new values. Usually, it is a good idea to load a separate, environment-specific configuration file. Flask allows you to import multiple configurations and it -will use the setting defined in the last import. This enables robust +will use the setting defined in the last import. This enables robust configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this. -.. code-block:: python +.. sourcecode:: python app.config.from_envvar('FLASKR_SETTINGS', silent=True) @@ -74,15 +76,15 @@ that in all cases, only variable names that are uppercase are considered. The ``SECRET_KEY`` is needed to keep the client-side sessions secure. Choose that key wisely and as hard to guess and complex as possible. -We will also add a method that allows for easy connections to the -specified database. This can be used to open a connection on request and +Lastly, you will add a method that allows for easy connections to the +specified database. This can be used to open a connection on request and also from the interactive Python shell or a script. This will come in -handy later. We create a simple database connection through SQLite and +handy later. You can create a simple database connection through SQLite and then tell it to use the :class:`sqlite3.Row` object to represent rows. -This allows us to treat the rows as if they were dictionaries instead of +This allows the rows to be treated as if they were dictionaries instead of tuples. -:: +.. sourcecode:: python def connect_db(): """Connects to the specific database.""" @@ -90,29 +92,6 @@ tuples. rv.row_factory = sqlite3.Row return rv -With that out of the way, you should be able to start up the application -without problems. Do this with the following commands:: - - export FLASK_APP=flaskr - export FLASK_DEBUG=1 - 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. -*Never leave debug mode activated in a production system*, because it will -allow users to execute code on the server! - -You will see a message telling you that server has started along with -the address at which you can access it. - -When you head over to the server in your browser, you will get a 404 error -because we don't have any views yet. We will focus on that a little later, -but first, we should get the database working. - -.. admonition:: Externally Visible Server - - Want your server to be publicly available? Check out the - :ref:`externally visible server ` section for more - information. +In the next section you will see how to run the application. -Continue with :ref:`tutorial-dbcon`. +Continue with :ref:`tutorial-setuptools`. diff --git a/docs/tutorial/setuptools.rst b/docs/tutorial/setuptools.rst new file mode 100644 index 00000000..306d94d3 --- /dev/null +++ b/docs/tutorial/setuptools.rst @@ -0,0 +1,89 @@ +.. _tutorial-setuptools: + +Step 3: Installing flaskr with setuptools +========================================= + +Flask is now shipped with built-in support for `Click`_. Click provides +Flask with enhanced and extensible command line utilities. Later in this +tutorial you will see exactly how to extend the ``flask`` command line +interface (CLI). + +A useful pattern to manage a Flask application is to install your app +using `setuptools`_. This involves creating a :file:`setup.py` +in the projects root directory. You also need to add an empty +:file:`__init__.py` file to make the :file:`flaskr/flaskr` directory +a package. The code structure at this point should be:: + + /flaskr + /flaskr + __init__.py + /static + /templates + setup.py + +The content of the ``setup.py`` file for ``flaskr`` is: + +.. sourcecode:: python + + from setuptools import setup + + setup( + name='flaskr', + packages=['flaskr'], + include_package_data=True, + install_requires=[ + 'flask', + ], + ) + +When using setuptools, it is also necessary to specify any special files +that should be included in your package (in the :file:`MANIFEST.in`). +In this case, the static and templates directories need to be included, +as well as the schema. Create the :file:`MANIFEST.in` and add the +following lines:: + + graft flaskr/templates + graft flaskr/static + include flaskr/schema.sql + +At this point you should be able to install the application. As usual, it +is recommended to install your Flask application within a `virtualenv`_. +With that said, go ahead and install the application with:: + + pip install --editable . + +.. note:: The above installation command assumes that it is run within the + projects root directory, `flaskr/`. Also, the `editable` flag allows + editing source code without having to reinstall the Flask app each time + you make changes. + +With that out of the way, you should be able to start up the application. +Do this with the following commands:: + + export FLASK_APP=flaskr.flaskr + export FLASK_DEBUG=1 + 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. +*Never leave debug mode activated in a production system*, because it will +allow users to execute code on the server! + +You will see a message telling you that server has started along with +the address at which you can access it. + +When you head over to the server in your browser, you will get a 404 error +because we don't have any views yet. That will be addressed a little later, +but first, you should get the database working. + +.. admonition:: Externally Visible Server + + Want your server to be publicly available? Check out the + :ref:`externally visible server ` section for more + information. + +Continue with :ref:`tutorial-dbcon`. + +.. _Click: http://click.pocoo.org +.. _setuptools: https://setuptools.readthedocs.io +.. _virtualenv: https://virtualenv.pypa.io diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 77991893..d6558233 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -1,11 +1,12 @@ .. _tutorial-templates: -Step 6: The Templates +Step 8: The Templates ===================== -Now we should start working on the templates. If we were to request the URLs -now, we would only get an exception that Flask cannot find the templates. The -templates are using `Jinja2`_ syntax and have autoescaping enabled by +Now it is time to start working on the templates. As you may have +noticed, if you make requests with the app running, you will get +an exception that Flask cannot find the templates. The templates +are using `Jinja2`_ syntax and have autoescaping enabled by default. This means that unless you mark a value in the code with :class:`~flask.Markup` or with the ``|safe`` filter in the template, Jinja2 will ensure that special characters such as ``<`` or ``>`` are @@ -57,9 +58,9 @@ show_entries.html This template extends the :file:`layout.html` template from above to display the messages. Note that the ``for`` loop iterates over the messages we passed -in with the :func:`~flask.render_template` function. We also tell the -form to submit to your `add_entry` function and use ``POST`` as HTTP -method: +in with the :func:`~flask.render_template` function. Notice that the form is +configured to to submit to the `add_entry` view function and use ``POST`` as +HTTP method: .. sourcecode:: html+jinja diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index d70b4abe..c5ecf7dd 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -8,3 +8,81 @@ expected, it's probably not a bad idea to add automated tests to simplify modifications in the future. The application above is used as a basic example of how to perform unit testing in the :ref:`testing` section of the documentation. Go there to see how easy it is to test Flask applications. + +Adding Tests to flaskr +====================== + +Assuming you have seen the testing section above and have either written +your own tests for ``flaskr`` or have followed along with the examples +provided, you might be wondering about ways to organize the project. + +One possible and recommended project structure is:: + + flaskr/ + flaskr/ + __init__.py + static/ + templates/ + tests/ + context.py + test_flaskr.py + setup.py + MANIFEST.in + +For now go ahead a create the :file:`tests/` directory as well as the +:file:`context.py` and :file:`test_flaskr.py` files, if you haven't +already. The context file is used as an import helper. The contents +of that file are:: + + import sys, os + + basedir = os.path.dirname(os.path.abspath(__file__)) + sys.path.insert(0, basedir + '/../') + + from flaskr import flaskr + +Testing + Setuptools +==================== + +One way to handle testing is to integrate it with ``setuptools``. All it +requires is adding a couple of lines to the :file:`setup.py` file and +creating a new file :file:`setup.cfg`. Go ahead and update the +:file:`setup.py` to contain:: + + from setuptools import setup + + setup( + name='flaskr', + packages=['flaskr'], + include_package_data=True, + install_requires=[ + 'flask', + ], + ) + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], + ) +Now create :file:`setup.cfg` in the project root (alongside +:file:`setup.py`):: + + [aliases] + test=pytest + +Now you can run:: + + python setup.py test + +This calls on the alias created in :file:`setup.cfg` which in turn runs +``pytest`` via ``pytest-runner``, as the :file:`setup.py` script has +been called. (Recall the `setup_requires` argument in :file:`setup.py`) +Following the standard rules of test-discovery your tests will be +found, run, and hopefully pass. + +This is one possible way to run and manage testing. Here ``pytest`` is +used, but there are other options such as ``nose``. Integrating testing +with ``setuptools`` is convenient because it is not necessary to actually +download ``pytest`` or any other testing framework one might use. \ No newline at end of file diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 618c97c6..d9838073 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -1,10 +1,10 @@ .. _tutorial-views: -Step 5: The View Functions +Step 7: The View Functions ========================== -Now that the database connections are working, we can start writing the -view functions. We will need four of them: +Now that the database connections are working, you can start writing the +view functions. You will need four of them: Show Entries ------------ @@ -30,7 +30,7 @@ Add New Entry This view lets the user add new entries if they are logged in. This only responds to ``POST`` requests; the actual form is shown on the -`show_entries` page. If everything worked out well, we will +`show_entries` page. If everything worked out well, it will :func:`~flask.flash` an information message to the next request and redirect back to the `show_entries` page:: @@ -45,8 +45,8 @@ redirect back to the `show_entries` page:: flash('New entry was successfully posted') return redirect(url_for('show_entries')) -Note that we check that the user is logged in here (the `logged_in` key is -present in the session and ``True``). +Note that this view checks that the user is logged in (that is, if the +`logged_in` key is present in the session and ``True``). .. admonition:: Security Note @@ -81,11 +81,11 @@ notified about that, and the user is asked again:: return render_template('login.html', error=error) The `logout` function, on the other hand, removes that key from the session -again. We use a neat trick here: if you use the :meth:`~dict.pop` method +again. There is a neat trick here: if you use the :meth:`~dict.pop` method of the dict and pass a second parameter to it (the default), the method will delete the key from the dictionary if present or do nothing when that -key is not in there. This is helpful because now we don't have to check -if the user was logged in. +key is not in there. This is helpful because now it is not necessary to +check if the user was logged in. :: @@ -94,11 +94,24 @@ if the user was logged in. session.pop('logged_in', None) flash('You were logged out') return redirect(url_for('show_entries')) - -Note that it is not a good idea to store passwords in plain text. You want to -protect login credentials if someone happens to have access to your database. -One way to do this is to use Security Helpers from Werkzeug to hash the -password. However, the emphasis of this tutorial is to demonstrate the basics -of Flask and plain text passwords are used for simplicity. + +.. admonition:: Security Note + + Passwords should never be stored in plain text in a production + system. This tutorial uses plain text passwords for simplicity. If you + plan to release a project based off this tutorial out into the world, + passwords should be both `hashed and salted`_ before being stored in a + database or file. + + Fortunately, there are Flask extensions for the purpose of + hashing passwords and verifying passwords against hashes, so adding + this functionality is fairly straight forward. There are also + many general python libraries that can be used for hashing. + + You can find a list of recommended Flask extensions + `here `_ + Continue with :ref:`tutorial-templates`. + +.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/ diff --git a/examples/flaskr/.gitignore b/examples/flaskr/.gitignore index fb46a3af..8d567f84 100644 --- a/examples/flaskr/.gitignore +++ b/examples/flaskr/.gitignore @@ -1 +1,2 @@ flaskr.db +.eggs/ diff --git a/examples/flaskr/MANIFEST.in b/examples/flaskr/MANIFEST.in new file mode 100644 index 00000000..efbd93df --- /dev/null +++ b/examples/flaskr/MANIFEST.in @@ -0,0 +1,3 @@ +graft flaskr/templates +graft flaskr/static +include flaskr/schema.sql diff --git a/examples/flaskr/README b/examples/flaskr/README index bdf91983..3cb021e7 100644 --- a/examples/flaskr/README +++ b/examples/flaskr/README @@ -13,15 +13,19 @@ export an FLASKR_SETTINGS environment variable pointing to a configuration file. - 2. Instruct flask to use the right application + 2. install the app from the root of the project directory - export FLASK_APP=flaskr + pip install --editable . - 3. initialize the database with this command: + 3. Instruct flask to use the right application + + export FLASK_APP=flaskr.flaskr + + 4. initialize the database with this command: flask initdb - 4. now you can run flaskr: + 5. now you can run flaskr: flask run @@ -30,5 +34,5 @@ ~ Is it tested? - You betcha. Run the `test_flaskr.py` file to see + You betcha. Run `python setup.py test` to see the tests pass. diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr/flaskr.py similarity index 100% rename from examples/flaskr/flaskr.py rename to examples/flaskr/flaskr/flaskr.py diff --git a/examples/flaskr/schema.sql b/examples/flaskr/flaskr/schema.sql similarity index 100% rename from examples/flaskr/schema.sql rename to examples/flaskr/flaskr/schema.sql diff --git a/examples/flaskr/static/style.css b/examples/flaskr/flaskr/static/style.css similarity index 100% rename from examples/flaskr/static/style.css rename to examples/flaskr/flaskr/static/style.css diff --git a/examples/flaskr/templates/layout.html b/examples/flaskr/flaskr/templates/layout.html similarity index 100% rename from examples/flaskr/templates/layout.html rename to examples/flaskr/flaskr/templates/layout.html diff --git a/examples/flaskr/templates/login.html b/examples/flaskr/flaskr/templates/login.html similarity index 100% rename from examples/flaskr/templates/login.html rename to examples/flaskr/flaskr/templates/login.html diff --git a/examples/flaskr/templates/show_entries.html b/examples/flaskr/flaskr/templates/show_entries.html similarity index 100% rename from examples/flaskr/templates/show_entries.html rename to examples/flaskr/flaskr/templates/show_entries.html diff --git a/examples/flaskr/setup.cfg b/examples/flaskr/setup.cfg new file mode 100644 index 00000000..b7e47898 --- /dev/null +++ b/examples/flaskr/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/examples/flaskr/setup.py b/examples/flaskr/setup.py new file mode 100644 index 00000000..910f23ac --- /dev/null +++ b/examples/flaskr/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name='flaskr', + packages=['flaskr'], + include_package_data=True, + install_requires=[ + 'flask', + ], + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], +) diff --git a/examples/flaskr/tests/context.py b/examples/flaskr/tests/context.py new file mode 100644 index 00000000..3c773332 --- /dev/null +++ b/examples/flaskr/tests/context.py @@ -0,0 +1,6 @@ +import sys, os + +basedir = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, basedir + '/../') + +from flaskr import flaskr \ No newline at end of file diff --git a/examples/flaskr/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py similarity index 98% rename from examples/flaskr/test_flaskr.py rename to examples/flaskr/tests/test_flaskr.py index 084f13f4..4715c417 100644 --- a/examples/flaskr/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -10,11 +10,10 @@ """ import pytest - import os -import flaskr import tempfile +from context import flaskr @pytest.fixture def client(request): From 1e5746bb2b0ca82d2bd77edf1f153fdcca70503f Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 6 Jul 2016 08:02:13 -0700 Subject: [PATCH 212/440] persona is discontinued, remove example closes #1947 --- examples/persona/README.md | 3 -- examples/persona/persona.py | 55 ------------------------- examples/persona/static/persona.js | 52 ----------------------- examples/persona/static/spinner.png | Bin 31201 -> 0 bytes examples/persona/static/style.css | 39 ------------------ examples/persona/templates/index.html | 23 ----------- examples/persona/templates/layout.html | 27 ------------ 7 files changed, 199 deletions(-) delete mode 100644 examples/persona/README.md delete mode 100644 examples/persona/persona.py delete mode 100644 examples/persona/static/persona.js delete mode 100644 examples/persona/static/spinner.png delete mode 100644 examples/persona/static/style.css delete mode 100644 examples/persona/templates/index.html delete mode 100644 examples/persona/templates/layout.html diff --git a/examples/persona/README.md b/examples/persona/README.md deleted file mode 100644 index d9482d36..00000000 --- a/examples/persona/README.md +++ /dev/null @@ -1,3 +0,0 @@ -A simple example for integrating [Persona](https://login.persona.org/) into a -Flask application. In addition to Flask, it requires the -[requests](www.python-requests.org/) library. diff --git a/examples/persona/persona.py b/examples/persona/persona.py deleted file mode 100644 index 7374c3af..00000000 --- a/examples/persona/persona.py +++ /dev/null @@ -1,55 +0,0 @@ -from flask import Flask, render_template, session, request, abort, g - -import requests - - -app = Flask(__name__) -app.config.update( - DEBUG=True, - SECRET_KEY='my development key', - PERSONA_JS='https://login.persona.org/include.js', - PERSONA_VERIFIER='https://verifier.login.persona.org/verify', -) -app.config.from_envvar('PERSONA_SETTINGS', silent=True) - - -@app.before_request -def get_current_user(): - g.user = None - email = session.get('email') - if email is not None: - g.user = email - - -@app.route('/') -def index(): - """Just a generic index page to show.""" - return render_template('index.html') - - -@app.route('/_auth/login', methods=['GET', 'POST']) -def login_handler(): - """This is used by the persona.js file to kick off the - verification securely from the server side. If all is okay - the email address is remembered on the server. - """ - resp = requests.post(app.config['PERSONA_VERIFIER'], data={ - 'assertion': request.form['assertion'], - 'audience': request.host_url, - }, verify=True) - if resp.ok: - verification_data = resp.json() - if verification_data['status'] == 'okay': - session['email'] = verification_data['email'] - return 'OK' - - abort(400) - - -@app.route('/_auth/logout', methods=['POST']) -def logout_handler(): - """This is what persona.js will call to sign the user - out again. - """ - session.clear() - return 'OK' diff --git a/examples/persona/static/persona.js b/examples/persona/static/persona.js deleted file mode 100644 index c1fcdb79..00000000 --- a/examples/persona/static/persona.js +++ /dev/null @@ -1,52 +0,0 @@ -$(function() { - /* convert the links into clickable buttons that go to the - persona service */ - $('a.signin').on('click', function() { - navigator.id.request({ - siteName: 'Flask Persona Example' - }); - return false; - }); - - $('a.signout').on('click', function() { - navigator.id.logout(); - return false; - }); - - /* watch persona state changes */ - navigator.id.watch({ - loggedInUser: $CURRENT_USER, - onlogin: function(assertion) { - /* because the login needs to verify the provided assertion - with the persona service which requires an HTTP request, - this could take a bit. To not confuse the user we show - a progress box */ - var box = $('
      ') - .hide() - .text('Please wait ...') - .appendTo('body') - .fadeIn('fast'); - $.ajax({ - type: 'POST', - url: $URL_ROOT + '_auth/login', - data: {assertion: assertion}, - success: function(res, status, xhr) { window.location.reload(); }, - error: function(xhr, status, err) { - box.remove(); - navigator.id.logout(); - alert('Login failure: ' + err); - } - }); - }, - onlogout: function() { - $.ajax({ - type: 'POST', - url: $URL_ROOT + '_auth/logout', - success: function(res, status, xhr) { window.location.reload(); }, - error: function(xhr, status, err) { - alert('Logout failure: ' + err); - } - }); - } - }); -}); diff --git a/examples/persona/static/spinner.png b/examples/persona/static/spinner.png deleted file mode 100644 index c9cd835849676056e750dcc08161f41b7aa7c13c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31201 zcmY(q1zc1?`^USxba%H%*V5hH-H3qF-5|B3(n@!CNlUYUh=P(Q->>LiW&%~Lt=R42unOGeyWgJXOOb`f!qpG5y2jnvU5;`jIS>x!r24tc>iY7k# z?sh)@)?T(CSvN;lYaiR!^uRai<-KgJecZk1{dl>BxcTT={r&y9Jsdn-O3Ou*}NG z4dvz+pnv7=>H!q7aj|7v(M)Cnte{)JGSC2WY#@O&dbtRIjMDDE*Zy6S`Cs~X4gv)r z?Sep0Q9o-zAdFwC3bF+2?jq&Mx_1y*PAg3Tc9xxa#z{qMsXi9VitZ&3VtCc zxge02SJ!AmcCFE)eP(O+3WI6(twBDQbFI;g4VR$JEH9ViEctlPF5N;^J*>o&mAAt4 ztAg%;hyUee@|~T(-}d;OAmce+|F_4cqXPfK&Yjhu!yCZ}qJ;VFiC#PpMwN{b3lACBcY zXy77#k%1kV;zfS#h`p#~P5!ZLYc-a@0xLl=p%B;Q4=jvP30B2;LD49MwfPqRTI65IRI}LF%nPE~i?&6s=Bal+|cWdVy5X^w+!(xnVz8GKbd$t|;bvtaI>=e(qu#*uD` zT#!hJx&U;E|CPhvh)Fb~;rEi9*qez$N2O&wxB+O_C>~Bw0i=aF74iSnjA=nR=uOT# zN4;~Cv)L-1x(BNg>9ajWPk`PK3?B2kCcRDZ;yq?Rwx`$F;7Cg&n|;dGk!c*?MD8ID zXxSE7c245A4cb;Kzk&t5T6@Xsy43pB*>j^na-Q&!fU-%I{EKoTURs)WTAKJ&d_Vnt z8%6?hI7!K4*|ta^q9KcnN>{PF67PaE{#B7TWVf-S9_{*1WucUQz0pf|;Xa3_$B)zN z{d_iSpTf_gtP{YsF5F2USbBUiLBhpsJ4A%6*e+FALb#!N!?YJUE`+jZ>_ z2?))yss!%sN!jg zg;`a)EwV22GB&d^4;>Ni)Yc_x%x-%+IfTEmeubpY-}8vvXi5I1cssoPKShm8NKP)I zQfW{o{&0~>t1#I>kgGpRK7YD(2h~PfUmu{`yb52l@f1GRJnvL(PX8225s!vWUD$z6 zj9^44S?SLHVi>@6P#E`|kfHjP9u*cH3GeJ3wkbh8$BH1|crm#8dlSyOq%D#X^^zUs zU)Q1?+z$pTinn@)e-W7mRD`GeP^24Ey2qiRpj8>-NQL~Z5}n{{y%IsbhOL@&`VI0C zL`V$e?&<{6H5!qB@iq;>Xmk3B`6EzAKbnG$PNy*J@zwP5-CaSxxN^&>s^rJC-0AER z<#$@+iY0glj}zmgGUw;?TH-i}?%gy05)W_}^Rz{#O7zFLQWkSqYT`#?k7g8+#cMS% z@aK1(8jfZxz&@m8Olx}g4~5n3x%uv%xs3bKr>GsKq?;DG^f7+?U+WsVmypQFu%b$q z7P_eqELEpB<+};r7Y9lU(IeOq%kB zRt3jBRbHmbI$NjsVv}J`GgJIW^6VjlscppjH(G@?`L%hY#)rwC(4>cJ~73J9eo7J4S==3Ig*PvDXcFz-=-1c(+ zX=>h|PL@li<50a?v0-fU&bV0mGxXs>`dMa6IF-uLP;g9A@DWLc=!d2p9iT-)Q?3sF z#h-M{?vYH!@i?+`Y7_ougCH~qVV9gU`$ckNhw0o&s+5bdhH%{p!E+uk} zQkm!Fx9pjpUBJS*2Zga`YAc%GFJtu64mFx*Z4){yfWxu6nFq!;cW2KfHUh8a$mp!7 zJXbAdDy83Xo4n46)`sU7lHe_DYw*lvBarL6Ve!;~Tf_5J`*BB4P?X~>{F#sS@6w8mA%VaeE=>G_D{!i0 zr*b6O1VI5=Td&)*zFg;Ck5vT}B5PAa%Jx7ii@IRx);o=??GEVp+wj2Mio{#qUj3}? zvrV+yzS3GA@H+XqJo#T?k3V0P8J>65A#1)?IhBZ8W=($-bB&w>0_+L}FHh zs0OqV_!58p#!$J$jeA4sL4#ERb&OKKuOBr)D#8W51LxZJInRuQ3>DJ4u8|@MrfOH` z<8uq)QP#HT#yT?CD(`D)89zN>!CqzrVe10#n)NlEnaznDe6%~LHk&(b4d$l-Z**-A`VL8O!E^z$lV}k1vD0kIV~sa88R^yh z<)zL#9ft%PU3;*zDK2(Ziv=y+WJ6kIo_pg1iGk-T%P!=Y8(nCyTEk+G3XfHudS(zk z=U&n2L*QU@*N@1BpG!p^s||umN)t2X!$|jd&MxR%oT@WE0QvnN6M}(rng1mq1CIYN z;p;2uFn|g7>|Vl;N_vSZD%Lu>qa+JYBQY?bqoOv78&te~1$r`fgT_3|WSB;0)# zXI-!PVEG3oarhDFBEA=VZ9X7?N37wkq!$xQ0u+QvBdy~yEbBKP9G3MA=|IQ-z$ci8 zXyB4?x|CDRI@m-ot(7zKA~j;ItcUR!dYcu^pw@s|CI-pBxmSlf>kg*5h@WI&%MA$Z zfTR=XgDUz>f*IM4j1qwNqrmHXJ}YIcd*>!JbaA~xq}&;(Ry|NJScg;X{mtr<4lz1r&1#L=;rlEz%2W00pimPsq} zT>zg)B`8nSmgUuyt$_28jlj9-X)R8U5q@yD^F@>@F*=s?y|rnV5};SC$6i@bcm{Cq z;)=jURyk)N;Rw#G}!YxQk-%b9Q0EWB^xNQ5UQUiZcKF{F$NLGB3` z*sx!f-8RaC4O!-LNC|tk zpHA?+IspwrweV@eA;Fj{IGot#5}y~qZNS7sa(zBea8IIzH2=;;$jBoFRc(~k2UYB= zrc`p@Ghn?$o;1Ys0rDlvV-*F!#+WV!<<~8fl^fX(p=^@pO?AVCFwH^en))C|I^+wo z=c=K@RXZLza>hHCJenoSw6qs+>8avVHmg!-g7J@I!oLG(#zgCpP70k8*&jX}%}k#- zH~AmQoMRmTb3D4Z?XKJgZ8hEXlhV{bZ8TK=&K*RYckBQ-G+ z7C`<^Qb1BhoLSO)ECcK*_^4$N}xmmgaIw>=!8+*q5~RsxMJH{Gk5+O1qJmQIjt{6 zw8lO~oIbcQYrcW4t#KvTze@!c@$t}v7;4*BMk1-%Nm`0Q&--bW!@+NYp5%?^-Pqee z8zLiw@fPVLSMJJT??iq4!;&fZGeFL&hiJh`Nx^hvF}xW~lpnEP^U>@IwJJ}Uv$8!x zTz-#oNGny=IR*BhyO1V%i*fUB-JMNg^>=Qcup!JEx}0QfIW-w}1jOX;pJ~kgQUe(A z&#!ePBlO8`!+ z&AtEON~(f?p(|bLGE^zb+6ARSmQ1Dus-~&Dh%En1!W}<=`S+HrRq=68cl1t(nRt;` zr>%mqs~>Nm*LB9juXvGF`-am8j8*$VXxt!_`g|JQC9ld#hWtxbGU030PerxjZLE-_ z;~u8LGs>)5i-y%z3(u~}zcar;2AB+3)6pNa_hKgXcB+L~Wdhbl60UwH8CZ6h1z0Xz zcD>zoYZxE&v?0-mvc=JD8PBz`SJT9cbvaUY9p=5yf9lWFAqT%bjH6P`~J zEf77r^~MHW{9PYrsuk#gWQh1K>-2$fuo*&PZo1FRv;{^;A*>`diy%=u`HWp>wQ(k^ z7C;rvwO_4FOGmGSK_6q@Ulfa*hZ2mc8^=R%g4In)(#MAXY~SX4Cz@%9tx&Fh21 zbdfdb^?(JK*Q$c^L&%ab`?A(%x>0h-=oQgNmIK^(GkTw7MYPNx!Fnch&?RY%p=UnO zO!QciA}`UY?BCFyXVfgC0ZhaCQ2-o%Vz$jO0jKZnD@90hP0g=;DGO%YX{+NK{|_7* zHFsyEDa(jxnTlw=uS9{;Q&%3`$bhU(BQ>xCzDOF5sr}6U@k5Z6+TKKDf*G;5OPVgi z?NVFyg?+M^?D5R5z>500s}OdSN8S~T1ekP8)H~|67$Ed9F`yd_jdktk^zCgztkjjR zi64s3S{+Y7Pe|IKN0-#xv~vLN{U0160p~LROF#x3|AE6;>-`J>4*&iS4w3)=aM-oD z`=zb{U&B)9b#?j=htU|T(*i=9#0cqk#ewM#&fkVAFkk%X+G>j;>?+3A3w1FyD~hb2 zt0`5AcN9*fEX^j<{3}qNs8jw0{y8gV5yX2Bl~`DoKExLP-m}gx_k_rGX+Ap)+$jh; z>;*n10wdxRysuV2kpxIY{f?XK4Vtvz3^e05QhxgAkOL`m^;cc)>o&a~)ZrUfWSZG` zK>Zj)LhstGxOAz9kcQX(Xtx*QHiD{EID$Sxp18BOe<|GvmVq3XQIDk>(K>J)kc5QU z4e-?}79lP?Px~mA?AhDmOfx;AT$2Wv@7wFumqTg4BCS_B_USKOMzlf{5#lljR_-W| zmVDzcfO6byL!W_iILBm&Wro*`KiZd5PjThH33S5}!Q)(TMmSw6z=og?V3w4!=ylFnIBRKFH4E@M$9YnEfc=C| zzrPf+3R?7FR{AEeoF#D?;ej5&XNV?H1+;}0Wlr%u1^j`nJAn9|R~>55=?%{>D97%b zRsNSKDkF;wH1^3pdDXnvkCY<#vMr`de$VFHUgxYTnEQTWDGXzZGnSnX!s>b?JZ@QP z7X!YL9WADx387ORxoPsrlRD0vqJk*L8 zYA|)-_){R1!-~V{2x5?z{P4r`5a+MT>lSzL$7l?sx|DWFt_W0EisJ-w3AS=OOX2#? zyv~#_X4fPAF~PBZQOk-CQ)6|`B?PG46nW)vs0D9UZ;_p|rI3zgchi^S7^saZ-2!AP`CkU#7(48OgP>SPPe3#Lk=GVc3;85(MqP?rk zKX3HEK6n9iUhjlr;ASucZv(&(6`N1$5yJ^@f=!xE`NeC$co)BC+$|}N?fjxGFSbTr zN#5KK0{xZ~RXNl9dP6a4**o=;Q)>x^S+;4B$Oj$65LO5kL2}t#Xz~~FKQv5BZq$0+ zGNO-;Df3OBvLxvQk}STG9dD0VC)_9RBTz=1NF7HU z@U)RGy0Aa3;4#hQ`CT7x(7;QGKlY;9m%^?tZ)J=3aGft zG5#wQ19`CtUslFW;wMuP zdQaqQCBfHbW|lkic3qf#`CXQ+b2>wE#e#N7W^Mu-xv5ZL1}z>uDLeY zx^x7#F}A(pbqKQmY$;3QXSB5t6g1sl^!RFgxt0c@Mpg8t#~62Y%Ww;0`I2S5obe&f zV<9d@+Y#wsTPu@hM+WGYo|j{9A-qLyM$T35 zH|G1!m4m9>dGzT>G+Ps8n<=J<^PazuYE})D0@StC0%73^-6FbYtNFf?o9b?20$y*s%LSZ_U3=#uTjbt^PPHO!Zd z*gHW(0pd+xNdhH~+-4Qd!RtsG2od)(HhbR|aGT*<@m&_7*!<$JfL$7k;g=D8%@k!}4Cs~ih3ILUI1Wa^{PU}fM1 zOGJ6IY`r=7h)5cKELtP>+Q4Lv0`#XVq>5iGV6XL4zq+=zyH$|?6`7Bp7M_dohd&f@ zGhe`EJH90@Lqykf$)V&#Iqmp0Mly@hH1AGMkw;PT891HQZI5kTp4H^+)ztbG|CoCW ztJId(^to6(4(t>>NSkVHgC|xYs}N7<8S46lGZ18KK_;Vn_8~3Nwp>IRzGYpqU5CLX zEke1wyXY^QE44?erCQ2&Ug(4{3)Hg;jLW&=tPjTWDMeT0U+La+{$V%ptE5zf6K2?A zj@7*4e(`DXr-f(Dq|?wZ5W1#!_+r)PuOD)@J)IuhvH+$J8PPJkYGRum#vxL)Hy)tD8 zf8Vf7BX2_HOUyjL>O?w3+->@1&E%rfGV#<3t$gPILW|@#|Kpln#$=96!8p_qzcxpm z{9BSsv7>$Y4i3mY>itCtJHUtod-Pz`z9u|o3t=t6`J-s-D2j3!bj=_l@_72Y*z+Ef zpZ1{+0(HcdInKV3A=pzre_O-=XP?h%QA(E+Hr7y8+viw%frD2FV}jS}Wjo`sMJI|~ z+IAM7@*V2KG|u#Fhi2&<+ZHVQ_*mqYNboDm++@g=^rc{&=4XZkuf26LI~7zO$KUTo z#65qz;cHE<`-Ow8fMI0_se^-U=Jz^w_2$0Ny(E{;$l55thj)g>^+lzofi)*hY=E7O02vwH7kt&?3Sv z7XJ*)NR9{t1o(e!hyt9;{4W6+aQxSCSX;0Uup#1qY>4_FHmoE5Cn#sG4v@*i@#zR% zzf&QZvBs8FD5O#_eWoPY!!K_loY9MDm*rF^$j&g4L$_r!jETEYW~g6sB6;@0Rkd`^ z!LOvEo$Ub}*M`gV8UOMEr4WyO<hxpy9}$Kd2v*NQx}{ z6TDUx;nLwRMtv*)>B$B(BM?9EkpYc;=s!Uj=Jf(AfNB9(19A#S%E$bPYdAuIBw=v3 zjJ97THnC?olAWDy&y2!4-)qu&$S*^2ZKHp7G3x97om~gJ4_5&1(g`tXI1*U*S0wcZ zpGH04w*vJe^^wZ9D|Q|(wDyz&jX8mT5gLC5>xR87-^w)umzW@%5XlRnn51O*V;?H> zw6K2u_A^35U4}S*66xb-j8Y5Xi!^NcpPm(<^;m`=F=O`L4;lU}N*giD0#y+IS z#07`~S2cr`E$NqR68gB|FwnBiS5o%*ahGCnO%gxaJN}wnK+b(@dhP!gtqv9S+0q$Y zUT`Rz(s1M(fAc}x5E!AHEe2@H?`%9lCRFlv;7_mjZ7}e6OzHuFy70NRQPV+2P%)-X z!ByjF&%x*!GYjHQ-|8jyIMS%EerE$PDk?pOSKj46&OCeA+gFX74rt(bIm>3mMH23) zZ?s<^?;98R7oPUCqpA?c55XmQ`Od*tUoe)UF5sWvT`q)jv`aSFEboQ)!h@}Bhue7o zT{#z=JMBzXbQrvESm5v7outro&3-JG28FY4b&-9U0s#a02mXt^*|k&8QQJg*xV?Je zk_H}%xPy1LRd~WHl$LaX_6p;52u1A>7nMmI*=_|M%>Bls|4Y68BM`YAdP+FxktQ@& zYO~w|cnvFI&}g2jh0F8{>5|7%q4BE{G^%K8gsxR0E3wi6Xw+92G|l`}nrtaQx_FSI zeQ~v(q&NBbYomlQdtjn4d%Glsr1=s3TE`PXN6jY-sh?F_M}*P=Q|@+bjWU625Ae+r zohoq!s;TzUUbKJ-QRNTKy`K7i$9Nz5v0Un(fN%pmF#roC$5O0F&2+VeCSZJT#?<0N z;F2Xe0VIH{Rt~Yl;1heOCK6*f(n%3gha1G;4UD-P5GAp$SGqSwe>Jh@byJ&& zSyfmy2i~!_x`s4H484_>`8Z(OcK5Iw`F*p*L?S)S^rz<+zEiuAdAFm9bTw~a@R1Ja z$0z7_-Z}(b?5%kPeny=wmN=4V4L4P5WxyO#g_rg}`Nq8UFAj-n11#jo>VSZ0MbXQ& z=K1S$taB2L_$AIc?gyjxO?Yy6V(-@J$?Z5P%0C-tyId!?vwB0N)KWcK#PQkpXHWy0 zox$+%a6mw!pbf({Sv2RSb8&O1g^8u@tiCh)T#g|wj%&e;87*I}pz$v%MByzWrSCx! zF4?=)yq-jV$w`kHy$UCr+GGA^um%KK&=!5NSd+iUAV#2juy`oWA{Yj+9imQqR2>*Q z-yMJQ0A&Hj6yBzC0(V!z2U+a9rc~_Ov-!)6u}IZx0QqP}&+JNi+iG)j+=!wF0ok}h zcDNmD*LLyUI)vlR*w)pLzXzGhrbhIID^#B}@1H?sxBPbA1ZAdZ-f<$y&tQ)xcjb ztPFm7IeKMyaC=Gd$9S|&P0g_T5pWZyrW?1nz>X$m3Q=;zX+ag3V{vMkzQ|0rbYcNP zZu&FsFp@#2l*$m#Pcgl|u)foWx8eO$VgXYut+S$l*sQRJkVR9wWM>m*XZxo$+rdsB zB*e5o#q7G^>^9zP4L1M!VGG2qv`*QIdWvAzK|XQDK$+f>H!P0`ye_ZHcYTQCQatOM;&i5w^ehTgS7i*8=ThF(VpW4An-PcB96a z*y!@3jRy7AMyFaWoDCMPMoQ(UQGU2555bMNI}q!?&vL7GAR1?FX~9arKb$iA9KXUm z)`X*yrmW$S%M}LG5yyCn*$AeP9Y65y^0ngZvZaysqh)wcE54m#s8ymB4^JNyz(M1rh8736bQ&hNKRrI9mBHgH8$GJmUURRc{31u<=PI>d+ZY%ISb_rn^>!ouM% z+8*RqKR#yjad=bHSckS3`O%ul^QXn;h;)pW`KRsz`o3G2RmV=8%?@aSAY8x)@*H2jYg2#BF|i*DMG;oGC#JcY=jWd_7oY#Mk55l4t(lCnrUnLz z>WyO*sn%?xA^8_aI;QU_4RZGWl%Dtja4KgfB zNdkgbGrz0bSph<0P;&B0o~P~5Ffp1v?}`t2Xvo6ir`bYl4=tuVF5j=dSk zWczBrp9U8+LwR+fLs8yC+(BiswomDwMh@1IJx0g(#im8=d#>TFt(m2OSv74Lf2mN~ zXh!}8Pyd|+W_}2i09m!BGgOx@*bIFr0A+zj{<7E4&5h9KZ!Jspw%MvR-Po?>#eYO) z6xr^SE~Pr6iba=LeOdA^cW`asEz8PgKRgxnY?*~$7Mc-I->-e8XS?UH`XlE==iqA| zrm4M5N=ldQpg{L2__gbv8Hv0QR!Ek5^!YkA3V2t=i`bH|g2KM%$1l|qNeLL$4?fMO z@%N4p3;B*plsC)f1sU!x{coP!jH&A-4G%CY<=Wk2G|tkX<4^z!YoX%}BS;&qxy!pB zk^*XCPO6P7TB9)|)D7`_sv;25f|HGs^=aOeHn-Nszeh*_X@F^qmuZV%ZFJJt|2YjX zo^|p+sv%AieBx5$yH0L8S$6Z7jEr}@2@Mx#(x0uoIzNZ*Is)AHe?*7|oXh+#0U2=o z*IOv539ur@%>RfG{r^LRR|mKuyr>uqG5Gv}`d|*G4|6!v$g}u@tbg(4Thuh1#~t)* zZQ1myl`t3-*bH|_>TKp)ooZ>Ae*DZ3a7y?>ra79kWK^ZMITiasDGk5z`u5P=?Wv=? z$2RPHe)fx^uxm}rQ$HZSupnrA#e1@MCjs$XzTWf?Ca&Y@u_={l68?>H_lYD# zANY0pHhT5(FIoRWlZ1QqY0p6D@n2K$=oMPp(K_-y5fB9srgda9oUjv!1v`LCr|od< zrN7k1)amO7EnrCC=3)E}M|(q3vlmbx4036BqJ|Xc7hH9n_5_*++UdbgL(Fd;`QcP> zVe=d^4j^ExRt7bg${~MW5xzWn*3LdD&d)V83s}QDRz`G@H(PJrrT#cgqM1KqMxyE# zzyo~(^=pm^w?P!DksIe^goru>x!jQsWqDdevVJ?sS1{45mG6WbYYeikYrCf817yW~ zERVD}Q(_Uy&vj&(hfuSUs0hJqEJk}ACrAC2F}nl*%bCE>A(N(d1hGY0OdP%94{`u) z1;&F?^;e&dZY8%I9|_`iP>JBr6rc&s)7U$)PLA<69rzAeA}X^{iHPsUPRO)_ zI)payKS3(sPU)YQ&5fF~rxO)xw9cFEOjF6_z>|K0>e))?)%TDd2}FA`fGPQxr~-U!a3 zoN=T6wB1!XZ{d&Bh-HFW*U)*GV3(ssdW`?(03r~P{DK|5;g8UJ)^tl0%XdVm9r`zq z*hn~_(X(w(L3kbK=$5~j;H*Vv&fXfR2X6%>&&XYjW?@z%C=X(Q&xyi)0QY9r(Um=J z27a@gy-4u=liaB!P=_>nqO?R*GPiWSS8w3vaD5q;Xr7OqhbW13B`4ae-~EeOsNQ-4 zG+56{n!F4K<_lU~>_G?u?wpYL;z!#<%VdLcwLLap9r4zwr@8vLZ)Iuy1?u;She>#J zr`nc7OGa2!;AzqNcUQO<3Gx*vIo76$0DtxECCw@$d|)6lA<@_0P3?sp`IDrT`lj-@ zFWR)QhJR^2C&qRu$;Imyl%DXl<{vh~9^I^@FHv;*!WfThaRD4O0`8C?k^ENRd{Oex ze9Nzdy4**gs?NhUU%>P3KLsqc!=!64RtiaY3LFxWk}P2ps;%1q3y%HX zttLenY^tN=If1nV`wZaKq(x}wihjjPO522mesRj&!e1%u?_t5A+!+@*8zQlj_oljS z2Q$(5VRY2g!M01Sj{Vf+r*_wi8gOj`yp%l>Si=k*+U;%+R# zSNv$Vk$%95ALBi3AFjxyi3T$r^aLPIve;=^es^qn#w;fkuZ~?|`mN)2xtTIMc4aj* znEV>+AQde{PCdU=J65w*oAzAsTSJYjd)<=XuPLbyC{@t!rDs0Qv6FMKq_Ih&q*rR0 z$%@9jG_Hj`SdMe$h=*^VmjVTQh(k^(zeog5?Zj^&`&8?+9c)9`h$@h@LGFgG zcM&3k4VXICBKpxA@$j)M{?_`eN?e*QL)FF1dNoy1$IH2T=PZwQRbIOKsLGo5RbH<7 zP!8&t)M?{sCV&h#;iMJXPh>au@&aDqj0-b0DWX%AK$6?jSKAx;;{Dw_U3;Vu;S?#{ zYwD2GkVbsP5w|+#Q;4+)j9b1N&sEOxgvE-P`}2Tsf^;wE;>^6zO8P8%Y|=p%u6u9Ec<8ULcwsnXK& zC9Zi{*(%xD-cE<^oMvo`!d;_TYam$Rd*Q8Qjq-4B-?vM4U~$#Z0S|Nl5fw!yOkP}P z?@Eff{pnE+W_M1zZVScFV&dG>ht;?i##~pYH|}hX_il zi%di&ew%N?@D`jm@>%LyUMI?`otzU6B`(=jVThZ9?357EGbhX#==$X`5+w0MBxq&= zkwspLOaSVxl%t!D9`|5g+hWw>>r{inK(*1$nn_GuVv>@ocb1eiSS)`R^xl=c;#FhO ze#0WxJ(GaWiaBBmXpf1VDb4FYEmUhJ%{~r;Srw+yJ*32VPgT{=Dq9t5Tru&J&IpJE zJC*;^Ob{dy*;{_HN+L1B8dFPy5@h(onE8())YH;vYJs|ZTJaTatw@jQi2yVtF8P`V z)wLF>8uVzv9BN5uU2+bqC^O&UZfRN9d5+NcJx>AaZM^~Y9OvUXKx-@vgGL*qmR8oS+EBDgDHHv z7p5%dPJ{p>9v*rokeq+Kt;5MC0d^S}>CGox3^todda?r^PtbK|xJze>S6_GqV{-@} z2uk^3l1OzVbF3?~ECHBPnSWUXH^P5N0o|3iXjIF3YPg3vQw%__9WIDGQdrh?Ooa z6HuyQF5?o~N4GW&g4L#{@&EO_$A|bqS7*)tXy#n5}wcaC{PCa(enIDkK$pelSwz zyO?Ls!o`Yv_A`E@+0E>82Hkrc9@pyIEjhA+KBDuUh$=>2gbiBYpGSN6u&&Ri|LST;hlw z5R8dN&q&)o8p(wv>7y2a9?Pli4ZBa5_+Y0!e8-p`bRH5@I4esHdR%wjdEAJkrFBb$ z?v@A->xikIfgJ-9>m+~LjwJFm+?Xnk$DqL9Q#V@A17%8Np(WfneRHLrdgr|2BL9*uu)&QX0>1WY0n@nr3?@{Pd{GmMwC8!r}+ER7?Kd^j%c(yT-+&%UpQ^k zhrjKhZIBkR6D?MxiG)73gy*7GZG_6rwJ_`}L)r9~EPZr?b1=!r&`i&=q8ETC zoCEHKrg%oTD$V@11GOO>gy*8jd4p(l*zdilw}rA(Z(gu7yFT`b6+BJiec0$G($7PV0S?50JCPs@BVE1o3XH&C zjku(#r?AqV1S=_I+8@;HbSmrCSVQEA$RZ5k|-3_tvPh@+bo*4lIErC+^mG9yP3){4`o| zGK-fIfG04CIl1K1SIt3RJi8B>W1v(u0T-mEw3du3VyqDsQ=-G zk3Wx*?krQ;_uy@wIP00bhi@!}d9$ml2ed)^O;>)tz9ie=1Kt5vv)H7sc?Ll~_3yom z#rayS76gQONjAf1Ow3OocH+LW`;opY8QyJyH^-Dbz~vZh^}6b~uygHr0B4|v{4^cx zG3BT~RoSw^x2$AWghbIF5>^>_T%_&L>3uHRl(;<*iJP`hD>Pm|RFR#%WS;|OkL#Ka z>`HtNsKjTdGtp^|f0|dwg~s`A&9o?`kli4m#-|Sog=w?|aV2L{Gm?`BAMJrc8wVCZ zA#Q|W&JjA(hHY!uwRtTjS0*g31cg2B5nV#QZvcr?SJx*$t|ZCD&~PYAR6yW2bjzj1 zhVA%AqoWkmCCAgWB&*7UcwF{v$^3_~AZqzS^rv`SlU)l~+T>0ee{7Alg&LHDR0x3Me*9c4yF&u^R2$XRb5UqM&nIHoD`dm_WIuDjl!K4aL*-Cdkv zeMQn}t~gCfqUcE*ObE2uSTKQR_uV84&Tu@8O0DlDiUxwOb>OUsETDB;C)WUFI)JR&dfFSFYXjRng4JvbS=2nKYjR+a^#Mz zrN|=l1FE!|*zB03ph5w{V-~=L@kgZ@W3j#ci)BE}#VG0Al9$##&<; zSG|ueBY0hoCvl!|=@6**xpM4TnQ(E7_ysyxUBwZTEjMcZogRz3w{2(0962dk(eFF3 z2dWq^->gngGHS)rYY@X$kjCmanrcB2MU`xU-Ufzz4s3KbekP6EK@Vx)nI4phdP=4J zgO-$|cA zsN_aRYGZWi?%bm#;ngA5Sq=-AVPMB6XHpFsLTR zlQt)o*;@W3`6K7s(V3|=G$P$r^!!@wLHA$xN~Gt}8!d1vwKh6q9L9`oJK1gv$uZXk z9M{XHjEPk#XE|%6*%Db#lsjh9++I|`indIoPkPb^_$ChN5nLoIS zWwI}8oMp_t6R(N+5G0{!FO$LH>r7>0V+~XxDmc$a{PVTrrfAV z1+;mhHo8mvH?qUrk0iHooLy-?b0%0!!^!q<3 z!~)J`{+ECZIR5JyT=>`sK;hB9KvJPRxyWZ=yc1fi-b*R zF|KlXaCkuH@Au!$$C3ZteC&NFezkITD7gcO#YDlQI@%JRbUN4+r=ru#;^&UvW=D=spHkpk2il^J&65mF{f7VS$ed*As z5gfk1$I|vJKNuCEuYhKGE+Gg(TT?vtF)_w}L4-ODSb%HLnPEtXeLg=W&40HiLlDVi zJ!%EK0=Z{qTmOuSeYM8%&-RhG8yHFag^3XLCOE(dIX>BL(ar z4dQqnAc!yy2qMIOJEe^@*9Pw=r3!Ckyx@ht|4$HM@S`STV1`zcL3#N?2E$ymou)1 z{^!QGicQyV5N8#!eB(^;WjG$p(eMWwJZ7mW^`A;j;Gqhdq=g$5T;-ki*x5JJ0TG14 z8nh9KfM<}Ny7b$LPDLju03(1HT`!=JZ#??d)4LP23!(K$OBeE(@NqchhcV4lKqfwd zP;zNWD?)Jr){_;SA2-am!oO8PAR^XvM z&o#w8XPfM%2~hd%^$I-w!eMz@0+J_mKrSyop18cbYs|PEhP5DYnanp{y^B5L^lx7Y zii4tS^(q*5$(Cu@5@GaV>;R(E&`o@;2=5SvkvuD0ry15UofF)3_MO7&)FX};2cd*fO&8>#n#c`{^W}=(q$LyO;A`vQ2=7tLTK-%AB8dZv_Jm>D^#D0O$+P5fSvpB z`^I7U1R5Qztw4K_^K=9EF4sQV1VN`o-IyvR>&(8KeNp7#Z-&-*z#pm>2|h=Ct_SbH z2SN#G>R;$6|0U^MNp%ch&RG|T-+g5oZx0LAWk2}u#uAj1p5fi&b}A3B{BN)N#suMy zj((6QnQlZcTuqlP9=(Qukx`$5`kpyIUz+H}i`)`A2fW$SE$2VG9T93< z1cUnC#k&e`KiZdPJWmau(MA&~%)!5i+PprA-px@6dGU-uiX*AO1+4W?7yd?yc>PZ% zb^)8on7<|)z1ktP^otubKrF*85E2Yq4&umILn_4{n8(@`8jEaNk{3HArHmoe^ku>P z2ScM*WX1e^g&!Y%u*6;ll<tUd4@x*wv5c&V7tG9rPqJQ82cUfTR z?hfge?oyEuq`ReCNeLIFLAo0WrMqhd0i{D)q(pe6mXHv^-y5IjbN=7+-*e{d9+;Wk znH|_`ulv4Ux4Ic<9K;vlXYhXWWa42_UC;A-Hq3Jb;P|6!W~!OHOkh&{@`ewoLChpB zNr^eZ9dr&VddnTnQ!f)Vt4>ifulAyMucUat*eBC@P})j*`&neb->yINcuIvaOM_~;%?Q#ZqQfa8rNfDyzD3j6 z*CP)zf5MskMV6r0Bka$84qk6Qh2a(K%eZ1!DntE%pWeQ{A-02Y)R=Xt-@K4RJ2{8l z)XNMLMvurIoGy11NpWf#cQL$BY8~r})ijBk#~H}gew={VC|iT zZ_6sR`OJwvfS^vscKHPi&pVXW39W^8!9?^OzVw>@1_u@9)CzM>SgNu{+E$^+r#_R%lUeOPT$oJA6%U@hNt>PVAllo@`ELKPIyrL8jh%E!vEu4FnV!A_NMuUV4o+gx^${^cJrpe`i_Ox z5+&#~u+ujBCSD))P2f#c_r@uFNvPDwO%#N$BL~ z%ltlx!m+jWF4rpO6MBc#?=_c@$sC{DuO&ew^;!9>oQrDlXPYmM#mdg6g=&u&gPGqv#3C`@H?#Nn z)7Ch$H=1+qg1N&Ol5%bPwfzv(jXTeCpG`>eNPK#%tb7jLiegUr@DgJ-g-vVXQ#L3a zo8Oj>XHpL>XZb>sp0yv4Y;~k`zOS?eEh>lFx3xHdHR`RaMrleO8@YLNhz#tvZu`Ns z=A};wXmr6v%!gJZvykX(`I_O*(gnL`kn{N$XHYHf`C!%dgD+Jw!dwcMJ>@}vyw>d2 z)E|8tNjnr6zwIW(5fM03vU&>wr>VR7S?Si!;Q8PeFjQKUwQ1_1md1$mQ;T_7{DsdE zUtm>`ki1(yO%zBVn~&CotbK3lvh{@j%Dm64Swrze3JnNEngs@qxZ~^3rg!m%eXc@1 zhW-k~|M)ccbg_k6Ffm_bFR|$@pN3g5G2F`@>@S3J?E_w?;$?_EE`FieISJGFJ9mnK z4QbFzNCW>uKo?)A1v#jt9sdkMnxUKSwe)9DKZk4t!L2~-N9GCj0|(PScf+2Ezs`g; zWCRb_TS>M&s$QD*+@|G^X3&;KG1EszJq#Y zZxV|J04|eqKOd~|v2FRKrMlrKe%wxgwtn~sz&|t`j-+B}GpsxHC)~>i`YqkW3;4Vk zn5k|Pu5_d=umj}+bHvn|ZENyzwlC0!H}}^TIjs@R;1g>wZt7*{foma|@F?-X25r&d zveq*GiF?*fBHz7a3HaCu{nbW8H+{c}T5 zF&n!yO-N@n`0?YW(A_Fa1;x_wlhE*NlW#E z5Q3%{{HOz^<@ht=wNx&ZcLvL}tw~7&L!*D#1toqdFP`0|j(t!0)n5jH)bVs1%;G_3 zRnr3O3k%-neQ+^~L*xmm&;6{@K5qMc5Ynp&#Q$YNf2LSNHn1TEv0I*>8~SONc*Uj7 zZ#mzlPXS#;Cn$X)LVJ;tJguv921_VxUEm~`z0A*eas+u}6hr~~3n?U)6qeXy2He02 z;9R~DMww0v+|XN5d(w+6Ahbbo3#dc2TRu$%Vl42mJ1ljg&^Ao^zTAsDYz3RgZ3lQj z`sF9cTYN}jwNws_YHGlT$)kxyKMn9dwT?v_WKTanam?@!a4uh0JUd(k*ObOlz&n{5 z=@b~80h0d2{~8;J!x2T{qUzg~p9+}}7pO}t3VOB(#$AI3LU2&ernuTlEdSuIQE2lM zHq#OXQW8bf;|DX$7hZeNiRDENz@naB;%|5qLg1k}h?8OpLTq0J*3?u7G}_4;{ym;O z*f?=S($3RljNz8I#ln-;)=$azSq^~#JVo>zSBJ|=1B(kts%G4rhxY+h>B)MQbI3A( zae^IdK5P#7yB@a{V?p+)RQ0KKH%38pUJ!4<9(i)IbcVj>;Yis9Hv(T5wi{6-YQtN1 z5(@hSsV^{5x*#3x^{Nx2IpiAi`E!bsDHT&~Zf;S^XzU%-?A}Wa%1NR(`JQ-$6Dsp} zvUf?!QBGc`dbpkyN9Aam&5*Y=1>9?TxRcNhmdnS>#@}hWZ0{4?7zV9^YNZ%EEx8h1vEv*! zY7&|ykJOThJIK0MX1rgxQAQAFeAUDNPYIEB+9!_bb8);vW%$CD7@%x+y~)StNZb-r z8BWpj6}u7lnoN5`GySm!_V^&wdpA09P!uNg*H2SRsLm2`LwODZAN^n_O0fMCez^cPowOTu=K1T>QJ>60O^D!r$kdjBzBd z5YTlAt@r+X{ECL&)BGU>hnvM5?VzkXtc#Ad|$P%WxTE@ppXEYd*;wRPk>*l|duF9+bOmG2v{=En&PwHN_%J z$o7Gy^m7l7Lk?07%{ZxD6@;2*%}rWHJkSUeJAx?$(o99l&5U*{}5_6@u1tqxUBG;kCBcsdZuKLYk$O@ zeIuB+l#pg$?R|M+WDQPsG!8pBG>v{Sk5{qI>Euhn>~s$IB;sMCXa`&V+r#vIUW4Qs zOPAA~Q`BD@!>y|lDo*t@o;^pij-0F!#K%;f3^r)2Y60PBuXs?jh6NE{2%$VrGACJV zgWr4QSr$M1Jf@Fjg43AvG2m-`wq3Q7%A5Vk$OG3)Ychd#i9jdYHEA4up0ccxoL$Or zrl`AWZb>Iy`^c6|>(xqQgEt3_g5ETh9RGap={nZh}$Z24V>xfyJN?P)h!w~ebvblXix{DxHq!2`Mps}UX2ruY0yFqZbK zHAln=BqwN#x?KnmHsdETPM7vlXyHs$T~Csh{cf}*fHM{-wCgs2(b8`U(z%dQN)S<-eT=XjMF$T`g?2; zL;fb?VL8@dza>-4BO7I2jZg8i(B*ZK_Rib}^aj3M&VBPuoUR9G!2hX3LhxAOzXjZZ z+rRc<7y&C#hyRNq6aA|W$^WtoJ7KX?X|6$GdyEsayrnuc^*tm@UFEM8i{C^%9(9R! zD$kmpQ%kBbwQ%7qdPraL(sUAsep;z{3~N@+WSY}tOhcr6nQU1p&ADi^HiFGD$~{c| z7GCHZaP_t8@+ZTz`~~~fP|n}B!Y_g2?c2HJK_4DR61brs#4%-Y^0$BcroyJRta3}K zF)4$v*}Y9Z9yB`GUIv_rX>koRfiv#LezMovPpwLlTKo#a} z&8GrMbAOG^idGQA5q{ud@Gj!yGK99S{J0Um0OVi*kdzI_qa|S97OI!DNSK_ta8O?I zal|dVkVj$PMxOBZ{mUDQtt;KXNfS0UBqKuLej+|67e~n!y=3j7AKESrUMo}`p#pjb zsv*`?kV>16&qJ4Ij^p2}{fjR20qDXoYZ-#Q*Q5_rv!1I*0HG&<%A;zqjI}umZ|ces zdN~rROT3v9+WZJrI|w22DftTj)hIY#1$Orbl1tSvpf-F9F1);%Rmjyg7l;{XZA5v9 z-L@4{2z@1Rt)K$RuxEALsdXMJGW6>1BZU+kq6jZMBYI^6bc!dr8@@mo{vaLLhy>)q z`RYK53ee~LN^RIpZMYo!LJplSs;J#kHH+I@zi0J6$d~{V&!1OdeqXz$lk~!R-zX29 zMByp9pJHqJY+L7Vf;t4_bdl3k)Xx!}8z6XWQpjT?<7=_ivCT7Yj=Kk^K)W9H>8y0> z^F=-YNE#}9xicOv4|kCy!8nJ|741F@ z)YHOlC&(&TbYB94lWQCHOxgboO^chWAiizy6smoG(ea5@5cv;~gy)W)GO4@cm=@?E z7-3vNNl#ZKi{%H&9xs0PLL0}hFwk+=(TQKq?-R6uiGAqmjfy5MUfGa7ifu7cHKkz->*beIzi-2x*xCvT}tlod|k3h~6Zp27Csb` z6=~@-YNuZcBkmFJ7RMz2Q3ptq^do7ksj@as9tQ;V=Cpo&PLx@&{&VQoh{I@x;gbuB z)sG*zqRZubHdtGM3qo8sIuls44n?}2s=YUIR^^;&zaOC2`$4U#0w=fY3HM)Y)*Wa7 z$1_#!DylaWjSkvFU`E~S|F8~Lp{?p!rP$d~SL;dDra|@K&4?-m=lnjf2>biKr8rWy zo204@DG%-@szGg)cBF40Qq0D$lLh038c-Udy}Mtd<H4Jh{9C$imVz74=EOb>Y_`sXx{~V8^nme20vLxyk2*YnMIt z7c3}G{AZZn1#BJx1JILW}(H{=LrjBX2ng%SNm?RrCT zby8=3N7qL7XDmFAbFp@K%csIjlWvJ)3Hs_XNE*n)m|J-mGqwdjSpTIK#@@=q-;{Y; zLH8DXOCH6nfHi-cosPL6E(TupSmNC&RL9WgHxz$3V9i}e@2{OUk|g>vLfT5O;PC!x zd*kU1ecoNW;R@9hcG=vMuCnfz4OQ40nVyt+c|4O_9Zmk|M$E3A{3^z85$}U~NX?(L zrJNodk=3;W$di18wQGqEE|3Aw0J>1Yc<%KM;govd)qJMRB05EiTton4@W~33Yap+! zXxZ)N_$v{<0?%8>#rO#Uhg})33+D6nc>`HFh}rmk%CU+# zJFahZn7_i^4qqObQACm4gK9QL0maZJ7e?V z2Fyv{?WOE>Y9xJM3mi&nz6Rih`%qs4Jm(bx|LAYn5t};)iDThR64-)Ivb%w4h}vmt zZS9?0TwI)SG&A#jANi(ubNa#N&giMgx`YZ;uRVxiX=C+%TOeLPgZ1)Bp)*`hX~7QL zfl%R<1v=9 z>s0G1Fk^lF=B;($xfT8oxLB4f)T0i+{{RBz&C6)dTw6W26U+6gQ*`+uXxA`;ZuH`L8+@$uUry9`o@_dB$7pv*O_Mu?oRFtm9Sa@lm9;a2HmE4bUFL))YywITk`OzPXsncTfU#BRC>g9>#XpZlE`_s7Z6 zalMH&oZz+TCTziIs+@QAkh08xnhk_Cl_5}-%N{aaIR(z(BezoxjRS4i3h zu#ef}l<4%x!5^T{P?a9N_6L)MG|z+v&ZOE;$I=_f1v2Bu#|+0({*hb95C%%hmB>|Z z(2e(M?Q;{JE{_5a28v@Xhqxbe*=@{+$Q!zwohXf9Ll!$-mKof(80ri4|3c(|9LoFs zOk0X@iFTUAf3QIRE6oB8N1OroEz6qVrQBM2rgcKJ(t2}Km+7q{WFoik^y0`w3AEtkg#180W8w;=venfA*oRf{s+ z2i=H%=vWwj3IcM5I6U-OsF}uY*4Gg$}+<*f%M!%zku- zch_#M9k_&PCgqDlwsC8Uf|PAS_6snO#}z&e=9<#6!2OFTG+}!Lo12dZh{Ed6XlrkX z@`Dj?)1C`$G|Ujv#S^tzA7kM?-D3?~x6P&_sR%yY^asu8UZ`Lc$jH^k2MCS*8)E{u*`4o+N*eY_zd7cQGr zpLKj8QC^s8l1O?XfjJv_cA4&8XZWO=k9Ey}Ht|{`ay3WC@9>6h@J6Ix8kzOxvEVZ` zNsKxn{s0r4%UU3~`{{AJwR$zOK{+>5Zy(>K_3HM^Bcvp~trP`GhDG=)O#)ytOaG?-!6rJ`9ZII0( z*~Gbx(ukFpM>mLpTZ?G)`zt~*817xbt`-ErkfP^Gx)^aEF>KDdKvl2P!NJ0h(`lCxQEdL&!zn< z2c5Mac~-q|E3d1q!;*VHsElb!7XOJ$WIJ35&QCKHv? z?<~wMKTo>|Pn#6WhQ?`We)-Z~+<6)D`-n2SSNOw5EqT@-Hy)A7f+1mWJVgsKV98S`C?IEn3roPN4>b~G;Lr%dVx<* zpS6h?KZ%I=I{xd}v%CqCk|bIE%MtrKcTkrQVy>G`%A1ujM1s+X-SujVH|}nDUw`Se z%&7TVpVV}SmFCy;Vo*X>1QdfznLnSUrW+M{>-PVY+ybrfGt}PVWvJx{sgC^`c-3B1 zS~K1nUI^L z*M$Ik!9`Tv{=*!y&f7I(1;uL>H^4r$1T(G7xOdb_TNJqo8WE9*d7n}eV^+tG+qL9I z^Q6@eS9|`Md0X4SyZbeFyLpeiX;Z`I_#63XZaax?je4vKBCW55_Gv0U4i$e|hv0q8 z9H0%c>i#K7(R?nSG2&Keaw`sbfH+i=9w*NvoQkvy&PdVXht30WC>h1|`wO3Ipp30( z4PsrV95xf-3%3X2upo46Wo4yPh^x|sK?@3#oLEjv&GOA{&kVOTJMo8mQZ(A zxQB)b{fxH9F>^O*6=Px+f7?xI;3}`>+sc$HDc;%JK^Rnw{U!X zUdn!S$4%|<$FHzYDOxUS;#vPl_W7Jcchu~-2RCY&zQ{&c$JXtS6xqY|ly9}6T#V|Y z32?Ej*v8h{$O8A!=EvtNLAzblYLPM_@oI33{a!-dKC$(3B|=#6);ugKE9M==l8fQ8 zjZ{QaKj~SW%oH@?x5Vlq6TYm7*VRlC+U!x31MM7eV%vC#B}L6LlZuavM@ay64~uKy zAwPJo(E68(QPL5;`6>D?$TSSI6q} zza;&@eY+hj?;Oa-47k=z+X28h1V^O{poMELOI}Ky6Jc(QT1HZn zCsq6sH7723(H-qi^D`3jLOoYlm%P%U0uS(ZG2Agih=DZpoBn`oP1UF7%SiElcFn&X zPz0!pL^Yk{3napO97|p#J_X@LvrRh5?lsFMxwh1Qi^=vFap@ytCO@3(*|!4#x{w_7 zJ4Z_PY8zG2Z~x}~EzD)rWTpwUA;Vx|Gt9-&+v(@RJ9>`4WNTCG!?lWmhTQG@ZYv*% zY62mo1^3p2M(y~?b$Gy5m~Arl3%HUKvBDNkM}lQrY&aQZw`^;DKVRs25(QOjtIW3m zb+8>MDuXZWcg*~>O?tB86@Ux*T}FU+8f_u=yomtorL6SyH;wXG@R3 zIs}mG;;rN0%wI^us019-0X2>614%1|Hg|P=V?Hh`JaExtn@O7RPmBXGc}p>zUn!+` zi1%FimtvR+b`22MMdq>a3&u)jm7#gfPc=XX{!by2g2xK~E#MB^{wl;MQs*V05dW7* zA^X3zWqZcN*UWX$SHF)sY>UWZh0L~DRwCO5=8SBxMIX+p zC|1s87;GlQO>YRNi&1bzymtFR)kI$LF4$YCNxOmgC2ilztwMa&c(S>2{uS?*RC6^n zE_+*B&h@)B4?{@-O!Zn#jr>S_{oZhjWKzzx{2N;wzy~nUv6$itiRh-_;o|UlX}Ao2 zW}liW4mpGXc=E{-CC}5|6VPAQo0_T~zim+;rmfRsa8B@N3Z>!9_*naFT;xm%L$t9r9lL%c!`#~58MP(-+TeEGIt5UNYAd*Se2$A9}ALsm* za=??qTugZ#5IG2rznDU6V@M_5lSV{N%CXp*Ch`ABI-5TTf>`5eA8O zsJuv`EXbXum2@pfqm6`4Mg8PLW{UwmC`YpYcYkcU)Mpvzm+()iNz+HsmG_@H2;mJ^0 zH|n_|nBv-fh8&OjIZ<@2#nXk6t9>N2-I|Jo>wAsOt3C5m6WK{_$obU2v zv_d(#YT+R>VF!#zAdB|dBh-c~5rIzbuQPB*me30OP(N_YJ#gB^!Is%3y2e;Rj0**c zQ!;C%$!>7A<$Mg8c(Ho!GsBB~@@*8PuOT1agAU~29IqR3dh`A8^&s+y7tj!uuPC2*fRT@TyG$&HtID|N7QJ`!n(4XJMUTcEP!c6Cf$+P^i4o92dv? z#Q#FdJM1C%G8PKgs`}&Bg9=75&cc{vw-?0pBm%0uqB7oRfrr(aIR)Zi5)b>;nJk1? z^XNhtS@>~l_$MwO`Q!VaS_G7NHgJW+X`$aw(?+gHqN!lqc$AP@l0VBJ5yAeny~qH4 zuoJv>T^QGLH3~zPq;m`j{jeyr1D{dx$CK@Yeu&0m0zx6)LJGEJIdY@d1iE8A;oZnD zJx?X)w(j8TQJobkyETejV$~71segM4k;N|j8o>x0Vm zwYqA$;_Lo}g@qCroFJ&RvU1n3;Kk`XW;DPr>Pau+T^&^}y7Z(p}XfZuPyF$;Ox1b=Q2bZ_P zlm^q@;T2Pb`5c8_{X#7NS6@KXgI9rw=;58;B;6{um%p3pgKnVS+Ko4;LJ!C2z*Fq{ z_`&{H{Hc370~;p#yn~wiw#G;!xz+H4nQE2}Q&@*gh;tVrw|$ znCUpCIjH%pcnf;5f=^5mE>VStl)JV7PRLCmoGCq^KMG-h-BiR3yu(y=!KE76b|^ah z;$;8lEcR21VKg==14+-SuO47ag+`Nl6u)&~>oCtC1NigsO_9%>v}A(m6Iu_%sm+oj zhqSGVGo^mYv*dIaI13=pTRAA&(Y68%3wxuCjpkaj#HtzF^1n%rGM|o}%K_Rox53cu z)((@lj()|D0L~hncS0klh0wgAVyhmTimazs9N?@2PA1{KZQnuOI18kTUJjdNKLBy# z$AA({RXZ@S6R$ble+aD^#e847$mEafCi_z+9nz^+RQG@!XY4j_Jh7{t-^#G>$6sTb z5C6hK_ZI!Mn3hfzx2_x6NFKRl1+69NMS2p+(l-dvqN_mkimVQZOy_tsvZj}x1`nt9 zYh-&rAMr8I%I7ollSgnrWWjRr!b>Em11kqcdi&`p#`dHNtiYbZx)$c@`aamCgKH6AmB)orRI^nUF7BbPi_yCHlPGaP#%N z6#SP3cCQLc*D40E>}-gx4kD;hR;EaYzWZjE>e!(;{myNhHfn2$%8t1Xc6BSNkPz4T@k}gwxRLt+TbE&#)+RaY}Xy5 zh3aESm5jjLsj+aAsey@>cjYhJCS?~mEa(@3xN`rFDaqgABow5w!Vz>pUe($r{bRx>tSM#IDi_59q=|8G)mKX%l31<~BAw>`*1k-4$zI zHfmh!jVHmcyBS!YUI^=PLd@Km0<|xCJ_WE1Z0;mwaGGVOIvua#{o~n^_&tkar>?c@ z)3%#p+kv)8czTbdO6PgBhjp6 z?>YA_g`O8gMnyQ`kyF>9wKn58%L8iOSqA}DkK5c$Va9G;g*fY1fe0S`B0=4Y8?W8J zny^yFqs4P2ew4|#Nt`pN*Y;IX(v|lP3XrWWmfWBCRRJsg{}YAe;IYDg3%CQfe{I7> zszM+N7yh#iDgLi*xY+teXP{Zx;Gu>1lTbCG=?=E004-Xj*S*S|-}P;{eX885$vhYD zR@aBI{AAWQdvCxiP;IarXYwM^cjn@ivptJy-UGKV?rcnUbeagFS87;({UDKIpOqKT z>u@{tly+s zbUa`I;{-uuEboqGRmYO!v6W)T5dJn*ZGWd9!p8*w&=e^4HE0S*qJ$a%2vBkZY(ejO z8CRybB*x=&6!nZe0NM9im}Fn|E*H3EZ~tzC<}_d(5pTJ_6LBfv~@Jqh6U@< z)XK0?hJa?>9|5S?1}-%vGK5Bq3I`B(c)r@Ocs<&$5j(^zF)$9LlaJ$(vOBgE5uEMW zi9fJTGe9i0_8zwG-LVg*(}aLHv^GTCf5Qo8zm?oNhdhWRqF?u{%yED33ZaM5mun&3 zi^S>j6!w~hP%h92xvG8=(KU4h6<`^r;dkz!DM=pxkHIFQGqj)iRMSd#){{LVxgjOR zT>vW(ai#l6Ar@yxAcGL*!V+PO>;pqipdf|<>l2J0ur4cceg3@?@Z;ol>fLH>mSt~I z)(hl9-$Rp~*m2C$QUS{P=NrMeZ5z}NeH}U2@2;e|60rJ&($#0v&~u)a>y#KbI8EWr z(*WQeG6VPUuG%DK6|6&fC+UQni-VJE9x5GrA6c0E^yxKKFMSLWEAdHej39sdHJE7GTh3rvbfFXxa z!PQ6yMwHD!i)GD%?_?GoBt8=Bi%0-ggv2HBZxk_fNLf`9z5x9Tv9@8``m$7@jiJ{X zkcv17_2X{oF1J~>ZE<+f{+k(P_2DX3#dk02X)Ny1AFQ87tazJN*DyA_gydr{P*48V zD;87qV*p1xP^W&M&eQTk>`#d}0xiZ7G91?133x7ER_GF2WjPi*?dM-xl`cY{ZZBS< z7(Al|P7KnCa` zcvsQ|4X!>oVYcMKvu%@dxArjuvax_X!ppWBzYXnz*^`sGI15=xz8q3-1IjC;NckT- zkrhF`bPl(ZKw6-;0Th)V5BvKBBfu<~)YKR78;#?EL%2JddsqkSW59jnc!NKh7e>n> zPX}jM>V5HNS7&&8N~kaU=ILc|SkMD7Hc_$jL(UZAKNL-oKr(8t86uxsQ>nNR=8)ZP z-W%l`ZO8^D;7xT0p4aRPWatiThrIiBsDi-YvUov5MHZp$eg~bqFe1jy@|D4d(@Zb{ z&;AKccrZwwggXD&eYEl7x;3;E%vMl@u^gg-f3#G=4DiX@+=+bb089^7@D4Dm71sih zcGj_xRG{GsOru1vy5GswTMO{>61topI&w=Cf5xMrH`d2qR>D%65*x}MN-P+k!2rd@ z9moA+%;XdSEKLF93fnOIZf&dR)( z23>`?M$CAdr6OJT23jancwd4kl7Niq0nE7X6u=ae^7rK195nAAVhrRYPtV4^&mr~u zQAPuGY;DoRYWe%O1>^VU@6ha$M0=J=Jnz7eSdl13%q%vJ`S8e_qRz4$kNIG9dBa4 zUF>0@3_@8=*7Pw#{KR_oMypKZ;S6hDR*~Ek$}Z@k(h7Es!8_7^*? zJ#dIwLz(Z_jOscEb>yTW7OS_zTXW@R;}b6vl(tS*xC}38E+A+w}0q?PiQiDsaZUni}z)g9H2$tz7@O#)*8Ihx1wg zIDy(8^qtKSUDJSNbZ_8ke_dUbOOg9upHRp`E>!c87HR&umD`cND^o?z?w4n+g(Gt} zy|zOP935A#`|rM_`v5$)^O}t}^bQW|XP?wd>i2IE15g1<@E3NHO+%{3FHYz$YI8h$ zdP>GR7#thR|BD^!NuJZpFx2HZtEdcawLn4!tw$;?R%wth4+_te`q#INsY58EAlVwm>c>w%tJ zFP^{T6C(jnO4P_>i}dCk2y9iy;QrQAua5Q*0kiqm93GrLFAHb{Ys9tQ)=7QG#P3OG z6E02h`4ztL`nWTn0#VQl2qxEn`f)-tP|18=ETAu!C^)3Q&S>l8RtuJb=_dnDefm0e2OppOZW@8p=>(#JpOV=Ju}@lmXsWJKi#PvKQRdp7>QCS{K?v1 zwaq>CNG*nAl25?8O^R#g1J1jSMjli`lR{@Vx!i>5OV3_q5+L*e$>hoEiqoS75%Ap`c a$v<*uh;o0K<$f#pRPSrvt5LFg`Tqb(kboKh diff --git a/examples/persona/static/style.css b/examples/persona/static/style.css deleted file mode 100644 index 6f0c5f15..00000000 --- a/examples/persona/static/style.css +++ /dev/null @@ -1,39 +0,0 @@ -html { - background: #eee; -} - -body { - font-family: 'Verdana', sans-serif; - font-size: 15px; - margin: 30px auto; - width: 720px; - background: white; - padding: 30px; -} - -h1 { - margin: 0; -} - -h1, h2, a { - color: #d00; -} - -div.authbar { - background: #eee; - padding: 0 15px; - margin: 10px -15px; - line-height: 25px; - height: 25px; - vertical-align: middle; -} - -div.signinprogress { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(255, 255, 255, 0.8) url(spinner.png) center center no-repeat; - font-size: 0; -} diff --git a/examples/persona/templates/index.html b/examples/persona/templates/index.html deleted file mode 100644 index bcccdfc1..00000000 --- a/examples/persona/templates/index.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "layout.html" %} -{% block title %}Welcome{% endblock %} -{% block body %} -

      Welcome

      -

      - This is a small example application that shows how to integrate - Mozilla's persona signin service into a Flask application. -

      - The advantage of persona over your own login system is that the - password is managed outside of your application and you get - a verified mail address as primary identifier for your user. -

      - In this example nothing is actually stored on the server, it - just takes over the email address from the persona verifier - and stores it in a session. - {% if g.user %} -

      - You are now logged in as {{ g.user }} - {% else %} -

      - To sign in click the sign in button above. - {% endif %} -{% endblock %} diff --git a/examples/persona/templates/layout.html b/examples/persona/templates/layout.html deleted file mode 100644 index 3fe46620..00000000 --- a/examples/persona/templates/layout.html +++ /dev/null @@ -1,27 +0,0 @@ - -{% block title %}{% endblock %} | Flask Persona Example - - - - - - -

      -

      Mozilla Persona Example

      -
      - {% if g.user %} - Signed in as {{ g.user }} - (Sign out) - {% else %} - Not signed in. - {% endif %} -
      -
      -{% block body %}{% endblock %} From fe53da45c56fbc01ee38f9fab762b7fb0bfbc4e4 Mon Sep 17 00:00:00 2001 From: Shakib Hossain Date: Sat, 16 Jul 2016 21:59:44 +0600 Subject: [PATCH 213/440] Update allowed_file function in fileuploads.rst Update allowed_file function to accept lowercase and uppercase file extensions --- docs/patterns/fileuploads.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 95c0032f..8ab8c033 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -47,7 +47,7 @@ the file and redirects the user to the URL for the uploaded file:: def allowed_file(filename): return '.' in filename and \ - filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/', methods=['GET', 'POST']) def upload_file(): From 55f9af72e35449718d405cc140c4705c12239ff2 Mon Sep 17 00:00:00 2001 From: Ioan Vancea Date: Thu, 28 Jul 2016 16:34:48 +0200 Subject: [PATCH 214/440] Added a missing module to import statement --- docs/patterns/deferredcallbacks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst index 886ae40a..a79dc913 100644 --- a/docs/patterns/deferredcallbacks.rst +++ b/docs/patterns/deferredcallbacks.rst @@ -60,7 +60,7 @@ At any time during a request, we can register a function to be called at the end of the request. For example you can remember the current language of the user in a cookie in the before-request function:: - from flask import request + from flask import request, after_this_request @app.before_request def detect_user_language(): From c54d67adee6f99113f525b376da4af27c3001321 Mon Sep 17 00:00:00 2001 From: ahmedakef Date: Thu, 28 Jul 2016 23:46:42 +0300 Subject: [PATCH 215/440] close
    • tag in lines (16,18) (#1951) i noticed that
    • tag haven't closed in lines 15,18 which is bad practice as if i put "some thing :

      some text

      " in the text-area all the other articles become

      so big and color blue --- examples/flaskr/flaskr/templates/show_entries.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flaskr/flaskr/templates/show_entries.html b/examples/flaskr/flaskr/templates/show_entries.html index 9cbd3229..2f68b9d3 100644 --- a/examples/flaskr/flaskr/templates/show_entries.html +++ b/examples/flaskr/flaskr/templates/show_entries.html @@ -13,9 +13,9 @@ {% endif %}
        {% for entry in entries %} -
      • {{ entry.title }}

        {{ entry.text|safe }} +
      • {{ entry.title }}

        {{ entry.text|safe }}
      • {% else %} -
      • Unbelievable. No entries here so far +
      • Unbelievable. No entries here so far
      • {% endfor %}
      {% endblock %} From 9359e9f911fd7246a2114d506c8d806090602f63 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Fri, 29 Jul 2016 05:27:30 -0700 Subject: [PATCH 216/440] Remove unused Redbaron dependency (#1967) --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 91a80c19..28942d3e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ commands = deps= pytest greenlet - redbaron lowest: Werkzeug==0.7 lowest: Jinja2==2.4 From e6d7a43ccd8e13ba5e4e3f7c1e95e90de6500b52 Mon Sep 17 00:00:00 2001 From: Auke Willem Oosterhoff Date: Wed, 3 Aug 2016 18:22:14 +0200 Subject: [PATCH 217/440] Use path of socket consistently accross document. (#1976) * #1975 Use location of socket consistently accross document. --- docs/deploying/uwsgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 183bdb69..fc991e72 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -29,7 +29,7 @@ Given a flask application in myapp.py, use the following command: .. sourcecode:: text - $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app + $ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi, since its smarter about that. It is used together with the ``--mount`` directive From 0f1cf50f97a617114078be3dbae3152339ff13ad Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Fri, 12 Aug 2016 07:12:00 -0600 Subject: [PATCH 218/440] adding in try around __import__ to catch invalid files/paths (#1950) --- flask/cli.py | 8 +++++++- tests/test_cli.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 58b6fb3a..9b8fa2cd 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -86,7 +86,13 @@ def locate_app(app_id): module = app_id app_obj = None - __import__(module) + try: + __import__(module) + except ImportError: + raise NoAppException('The file/path provided (%s) does not appear to ' + 'exist. Please verify the path is correct. If ' + 'app is not on PYTHONPATH, ensure the extension ' + 'is .py' % module) mod = sys.modules[module] if app_obj is None: app = find_best_app(mod) diff --git a/tests/test_cli.py b/tests/test_cli.py index 3f2cceab..d2bb61b9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -80,6 +80,8 @@ def test_locate_app(test_apps): assert locate_app("cliapp.app").name == "testapp" assert locate_app("cliapp.app:testapp").name == "testapp" assert locate_app("cliapp.multiapp:app1").name == "app1" + pytest.raises(NoAppException, locate_app, "notanpp.py") + pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") From 5044f3d6101e5915963a497779b6ad1166a7a388 Mon Sep 17 00:00:00 2001 From: teichopsia- Date: Fri, 19 Aug 2016 21:01:13 -0500 Subject: [PATCH 219/440] Update testing.rst (#1987) Python 3.4.2 TypeError: Type str doesn't support the buffer API --- docs/testing.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index fdf57937..0737936e 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -152,13 +152,13 @@ invalid credentials. Add this new test to the class:: def test_login_logout(self): rv = self.login('admin', 'default') - assert 'You were logged in' in rv.data + assert b'You were logged in' in rv.data rv = self.logout() - assert 'You were logged out' in rv.data + assert b'You were logged out' in rv.data rv = self.login('adminx', 'default') - assert 'Invalid username' in rv.data + assert b'Invalid username' in rv.data rv = self.login('admin', 'defaultx') - assert 'Invalid password' in rv.data + assert b'Invalid password' in rv.data Test Adding Messages -------------------- @@ -172,9 +172,9 @@ like this:: title='', text='HTML allowed here' ), follow_redirects=True) - assert 'No entries here so far' not in rv.data - assert '<Hello>' in rv.data - assert 'HTML allowed here' in rv.data + assert b'No entries here so far' not in rv.data + assert b'<Hello>' in rv.data + assert b'HTML allowed here' in rv.data Here we check that HTML is allowed in the text but not in the title, which is the intended behavior. From 55bd39c7f05e0845121028e143339f1092cbc9bb Mon Sep 17 00:00:00 2001 From: SaturnR Date: Sat, 20 Aug 2016 19:43:10 +0400 Subject: [PATCH 220/440] Update for python3 (#1973) just updated print 'Initialized the database.' with print('Initialized the database.') to be python3 compliant --- docs/tutorial/dbinit.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 09997906..fbbcde00 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -35,7 +35,7 @@ just below the `connect_db` function in :file:`flaskr.py`:: def initdb_command(): """Initializes the database.""" init_db() - print 'Initialized the database.' + print('Initialized the database.') The ``app.cli.command()`` decorator registers a new command with the :command:`flask` script. When the command executes, Flask will automatically From 9121e109bdcd8b2a50bf53cac73b470232d126df Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Sat, 20 Aug 2016 08:43:58 -0700 Subject: [PATCH 221/440] Add test for get_version (CLI) (#1884) --- tests/test_cli.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index d2bb61b9..18026a75 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -22,7 +22,7 @@ from flask import Flask, current_app from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \ - find_default_import_path + find_default_import_path, get_version def test_cli_name(test_apps): @@ -98,6 +98,21 @@ def test_find_default_import_path(test_apps, monkeypatch, tmpdir): assert find_default_import_path() == expect_rv +def test_get_version(test_apps, capsys): + """Test of get_version.""" + from flask import __version__ as flask_ver + from sys import version as py_ver + class MockCtx(object): + resilient_parsing = False + color = None + def exit(self): return + ctx = MockCtx() + get_version(ctx, None, "test") + out, err = capsys.readouterr() + assert flask_ver in out + assert py_ver in out + + def test_scriptinfo(test_apps): """Test of ScriptInfo.""" obj = ScriptInfo(app_import_path="cliapp.app:testapp") From f16e477b2a7329d249b5793a0dc4986503a48371 Mon Sep 17 00:00:00 2001 From: Nathan Land Date: Fri, 3 Jun 2016 14:45:22 -0700 Subject: [PATCH 222/440] Add tests for flask.json.dump() and test that jsonify correctly converts uuids. --- tests/test_helpers.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3ff5900b..f3b80325 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -12,6 +12,7 @@ import pytest import os +import uuid import datetime import flask from logging import StreamHandler @@ -99,6 +100,22 @@ class TestJSON(object): rv = flask.json.dumps(u'\N{SNOWMAN}') assert rv == u'"\u2603"' + def test_json_dump_to_file(self): + app = flask.Flask(__name__) + + test_data = {'lol': 'wut'} + + with app.app_context(): + t_fh = open('test_json_dump_file', 'w') + flask.json.dump(test_data, t_fh) + t_fh.close() + + t_fh = open('test_json_dump_file', 'r') + rv = flask.json.load(t_fh) + assert rv == test_data + t_fh.close() + os.remove('test_json_dump_file') + def test_jsonify_basic_types(self): """Test jsonify with basic types.""" # Should be able to use pytest parametrize on this, but I couldn't @@ -172,6 +189,18 @@ class TestJSON(object): assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) + def test_jsonify_uuid_types(self): + """Test jsonify with uuid.UUID types""" + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4) + + app = flask.Flask(__name__) + c = app.test_client() + url = '/uuid_test' + app.add_url_rule(url, 'uuid_test', lambda val=test_uuid: flask.jsonify(x=val)) + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data)['x'] == str(test_uuid) + def test_json_attr(self): app = flask.Flask(__name__) @app.route('/add', methods=['POST']) From f193f590bc33bde70fad200782350beac3506585 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 21 Aug 2016 08:47:12 -0700 Subject: [PATCH 223/440] clean up new json tests --- tests/test_helpers.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index f3b80325..610e18ea 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -19,7 +19,7 @@ from logging import StreamHandler from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date -from flask._compat import StringIO, text_type, PY2 +from flask._compat import StringIO, text_type def has_encoding(name): @@ -102,19 +102,14 @@ class TestJSON(object): def test_json_dump_to_file(self): app = flask.Flask(__name__) - - test_data = {'lol': 'wut'} + test_data = {'name': 'Flask'} + out = StringIO() with app.app_context(): - t_fh = open('test_json_dump_file', 'w') - flask.json.dump(test_data, t_fh) - t_fh.close() - - t_fh = open('test_json_dump_file', 'r') - rv = flask.json.load(t_fh) + flask.json.dump(test_data, out) + out.seek(0) + rv = flask.json.load(out) assert rv == test_data - t_fh.close() - os.remove('test_json_dump_file') def test_jsonify_basic_types(self): """Test jsonify with basic types.""" @@ -191,15 +186,20 @@ class TestJSON(object): def test_jsonify_uuid_types(self): """Test jsonify with uuid.UUID types""" + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4) app = flask.Flask(__name__) - c = app.test_client() url = '/uuid_test' - app.add_url_rule(url, 'uuid_test', lambda val=test_uuid: flask.jsonify(x=val)) + app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) + + c = app.test_client() rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data)['x'] == str(test_uuid) + + rv_x = flask.json.loads(rv.data)['x'] + assert rv_x == str(test_uuid) + rv_uuid = uuid.UUID(rv_x) + assert rv_uuid == test_uuid def test_json_attr(self): app = flask.Flask(__name__) From 3313b8b0a4bd60768b047648365bffd952411d17 Mon Sep 17 00:00:00 2001 From: sanderl-mediamonks Date: Mon, 22 Aug 2016 11:49:52 +0200 Subject: [PATCH 224/440] Use the correct Celery result backend setting --- docs/patterns/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index 52155f62..673d953b 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -36,7 +36,7 @@ This is all that is necessary to properly integrate Celery with Flask:: from celery import Celery def make_celery(app): - celery = Celery(app.import_name, backend=app.config['CELERY_BACKEND'], + celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'], broker=app.config['CELERY_BROKER_URL']) celery.conf.update(app.config) TaskBase = celery.Task From 5f009374fd28aba381f375391b70ac131926bd9d Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Mon, 22 Aug 2016 14:52:54 -0400 Subject: [PATCH 225/440] Update minitwit & improve testing for examples (#1954) * Update minitwit & improve testing for examples * Related to #1945 * Re-works minitwit to be installed and run as: pip install --editable . export FLASK_APP=minitwit.minitwit export FLASK_DEBUG=1 flask initdb flask run * added flaskr and minitwit to norecursedirs * tests not properly run when using pytest standards * see: http://stackoverflow.com/questions/38313171/configuring-pytest-with-installable-examples-in-a-project * Both flaskr and minitwit now follow pytest standards. * Tests can for them as `py.test` or `python setup.py test` * Update minitwit readme * updates the instructions for running * Fixes for updating the minitwit example - This reverts the changes to the *docs/* (I will file separate PR). - Running the app is now: `export FLASK_APP=minitwit` & `flask run` (After installing the app) * Remove unnecessary comma from flaskr/setup.py --- examples/flaskr/tests/context.py | 6 ------ examples/flaskr/tests/test_flaskr.py | 4 ++-- examples/minitwit/.gitignore | 2 ++ examples/minitwit/MANIFEST.in | 3 +++ examples/minitwit/README | 2 +- examples/minitwit/minitwit/__init__.py | 1 + examples/minitwit/{ => minitwit}/minitwit.py | 0 examples/minitwit/{ => minitwit}/schema.sql | 0 .../minitwit/{ => minitwit}/static/style.css | 0 .../{ => minitwit}/templates/layout.html | 0 .../minitwit/{ => minitwit}/templates/login.html | 0 .../{ => minitwit}/templates/register.html | 0 .../{ => minitwit}/templates/timeline.html | 0 examples/minitwit/setup.cfg | 2 ++ examples/minitwit/setup.py | 16 ++++++++++++++++ examples/minitwit/{ => tests}/test_minitwit.py | 2 +- setup.cfg | 2 +- 17 files changed, 29 insertions(+), 11 deletions(-) delete mode 100644 examples/flaskr/tests/context.py create mode 100644 examples/minitwit/.gitignore create mode 100644 examples/minitwit/MANIFEST.in create mode 100644 examples/minitwit/minitwit/__init__.py rename examples/minitwit/{ => minitwit}/minitwit.py (100%) rename examples/minitwit/{ => minitwit}/schema.sql (100%) rename examples/minitwit/{ => minitwit}/static/style.css (100%) rename examples/minitwit/{ => minitwit}/templates/layout.html (100%) rename examples/minitwit/{ => minitwit}/templates/login.html (100%) rename examples/minitwit/{ => minitwit}/templates/register.html (100%) rename examples/minitwit/{ => minitwit}/templates/timeline.html (100%) create mode 100644 examples/minitwit/setup.cfg create mode 100644 examples/minitwit/setup.py rename examples/minitwit/{ => tests}/test_minitwit.py (99%) diff --git a/examples/flaskr/tests/context.py b/examples/flaskr/tests/context.py deleted file mode 100644 index 3c773332..00000000 --- a/examples/flaskr/tests/context.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys, os - -basedir = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, basedir + '/../') - -from flaskr import flaskr \ No newline at end of file diff --git a/examples/flaskr/tests/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py index 4715c417..663e92e0 100644 --- a/examples/flaskr/tests/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -9,11 +9,11 @@ :license: BSD, see LICENSE for more details. """ -import pytest import os import tempfile +import pytest +from flaskr import flaskr -from context import flaskr @pytest.fixture def client(request): diff --git a/examples/minitwit/.gitignore b/examples/minitwit/.gitignore new file mode 100644 index 00000000..c3accd82 --- /dev/null +++ b/examples/minitwit/.gitignore @@ -0,0 +1,2 @@ +minitwit.db +.eggs/ diff --git a/examples/minitwit/MANIFEST.in b/examples/minitwit/MANIFEST.in new file mode 100644 index 00000000..973d6586 --- /dev/null +++ b/examples/minitwit/MANIFEST.in @@ -0,0 +1,3 @@ +graft minitwit/templates +graft minitwit/static +include minitwit/schema.sql \ No newline at end of file diff --git a/examples/minitwit/README b/examples/minitwit/README index a2a7f395..4561d836 100644 --- a/examples/minitwit/README +++ b/examples/minitwit/README @@ -31,5 +31,5 @@ ~ Is it tested? - You betcha. Run the `test_minitwit.py` file to + You betcha. Run the `python setup.py test` file to see the tests pass. diff --git a/examples/minitwit/minitwit/__init__.py b/examples/minitwit/minitwit/__init__.py new file mode 100644 index 00000000..0b8bd697 --- /dev/null +++ b/examples/minitwit/minitwit/__init__.py @@ -0,0 +1 @@ +from minitwit import app \ No newline at end of file diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit/minitwit.py similarity index 100% rename from examples/minitwit/minitwit.py rename to examples/minitwit/minitwit/minitwit.py diff --git a/examples/minitwit/schema.sql b/examples/minitwit/minitwit/schema.sql similarity index 100% rename from examples/minitwit/schema.sql rename to examples/minitwit/minitwit/schema.sql diff --git a/examples/minitwit/static/style.css b/examples/minitwit/minitwit/static/style.css similarity index 100% rename from examples/minitwit/static/style.css rename to examples/minitwit/minitwit/static/style.css diff --git a/examples/minitwit/templates/layout.html b/examples/minitwit/minitwit/templates/layout.html similarity index 100% rename from examples/minitwit/templates/layout.html rename to examples/minitwit/minitwit/templates/layout.html diff --git a/examples/minitwit/templates/login.html b/examples/minitwit/minitwit/templates/login.html similarity index 100% rename from examples/minitwit/templates/login.html rename to examples/minitwit/minitwit/templates/login.html diff --git a/examples/minitwit/templates/register.html b/examples/minitwit/minitwit/templates/register.html similarity index 100% rename from examples/minitwit/templates/register.html rename to examples/minitwit/minitwit/templates/register.html diff --git a/examples/minitwit/templates/timeline.html b/examples/minitwit/minitwit/templates/timeline.html similarity index 100% rename from examples/minitwit/templates/timeline.html rename to examples/minitwit/minitwit/templates/timeline.html diff --git a/examples/minitwit/setup.cfg b/examples/minitwit/setup.cfg new file mode 100644 index 00000000..b7e47898 --- /dev/null +++ b/examples/minitwit/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/examples/minitwit/setup.py b/examples/minitwit/setup.py new file mode 100644 index 00000000..1e580216 --- /dev/null +++ b/examples/minitwit/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name='minitwit', + packages=['minitwit'], + include_package_data=True, + install_requires=[ + 'flask', + ], + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], +) \ No newline at end of file diff --git a/examples/minitwit/test_minitwit.py b/examples/minitwit/tests/test_minitwit.py similarity index 99% rename from examples/minitwit/test_minitwit.py rename to examples/minitwit/tests/test_minitwit.py index bd58d4dc..50ca26d9 100644 --- a/examples/minitwit/test_minitwit.py +++ b/examples/minitwit/tests/test_minitwit.py @@ -9,9 +9,9 @@ :license: BSD, see LICENSE for more details. """ import os -import minitwit import tempfile import pytest +from minitwit import minitwit @pytest.fixture diff --git a/setup.cfg b/setup.cfg index 69feced4..47c225ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,4 +5,4 @@ release = egg_info -RDb '' universal = 1 [pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs +norecursedirs = .* *.egg *.egg-info env* artwork docs examples From e00e2c22aadf9e5b9dc353c31f1b70dad488b47a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2016 15:41:46 +0200 Subject: [PATCH 226/440] Disable logger propagation by default --- CHANGES | 1 + flask/logging.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 9e13bd71..1410a9ae 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Version 0.12 See pull request ``#1849``. - Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` (pull request ``#1730``). +- Disable logger propagation by default for the app logger. Version 0.11.2 -------------- diff --git a/flask/logging.py b/flask/logging.py index 5a1f149c..3f888a75 100644 --- a/flask/logging.py +++ b/flask/logging.py @@ -87,4 +87,8 @@ def create_logger(app): logger.__class__ = DebugLogger logger.addHandler(debug_handler) logger.addHandler(prod_handler) + + # Disable propagation by default + logger.propagate = False + return logger From 71e10be28665eee6a919045f9e5d8773508dc756 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 26 Aug 2016 03:08:03 +0200 Subject: [PATCH 227/440] Properly remove f.name usage in send_file (#1988) * Properly remove f.name usage in send_file * Update changelogs * Fix tests --- CHANGES | 6 +++-- docs/upgrading.rst | 40 ++++++++++++++++++++++++++++++- flask/helpers.py | 56 ++++++++++++++++++++++++++++++------------- tests/test_helpers.py | 31 ++++++++++++++++-------- 4 files changed, 103 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index 9e13bd71..5273e09d 100644 --- a/CHANGES +++ b/CHANGES @@ -7,8 +7,10 @@ Version 0.12 ------------ - the cli command now responds to `--version`. -- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``. - See pull request ``#1849``. +- Mimetype guessing and ETag generation for file-like objects in ``send_file`` + has been removed, as per issue ``#104``. See pull request ``#1849``. +- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall back to + ``application/octet-stream``. See pull request ``#1988``. - Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` (pull request ``#1730``). diff --git a/docs/upgrading.rst b/docs/upgrading.rst index a85fb0fa..6b933c59 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -19,7 +19,45 @@ providing the ``--upgrade`` parameter:: $ pip install --upgrade Flask -.. _upgrading-to-10: +.. _upgrading-to-012: + +Version 0.12 +------------ + +Changes to send_file +```````````````````` + +The ``filename`` is no longer automatically inferred from file-like objects. +This means that the following code will no longer automatically have +``X-Sendfile`` support, etag generation or MIME-type guessing:: + + response = send_file(open('/path/to/file.txt')) + +Any of the following is functionally equivalent:: + + fname = '/path/to/file.txt' + + # Just pass the filepath directly + response = send_file(fname) + + # Set the MIME-type and ETag explicitly + response = send_file(open(fname), mimetype='text/plain') + response.set_etag(...) + + # Set `attachment_filename` for MIME-type guessing + # ETag still needs to be manually set + response = send_file(open(fname), attachment_filename=fname) + response.set_etag(...) + +The reason for this is that some file-like objects have a invalid or even +misleading ``name`` attribute. Silently swallowing errors in such cases was not +a satisfying solution. + +Additionally the default of falling back to ``application/octet-stream`` has +been removed. If Flask can't guess one or the user didn't provide one, the +function fails. + +.. _upgrading-to-011: Version 0.11 ------------ diff --git a/flask/helpers.py b/flask/helpers.py index 4129ed30..e8422f7a 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -437,7 +437,14 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, to ``True`` to directly emit an ``X-Sendfile`` header. This however requires support of the underlying webserver for ``X-Sendfile``. - You must explicitly provide the mimetype for the filename or file object. + By default it will try to guess the mimetype for you, but you can + also explicitly provide one. For extra security you probably want + to send certain files as attachment (HTML for instance). The mimetype + guessing requires a `filename` or an `attachment_filename` to be + provided. + + ETags will also be attached automatically if a `filename` is provided. You + can turn this off by setting `add_etags=False`. Please never pass filenames to this function from user sources; you should use :func:`send_from_directory` instead. @@ -458,9 +465,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, cache_timeout pulls its default from application config, when None. .. versionchanged:: 0.12 - mimetype guessing and etag support removed for file objects. - If no mimetype or attachment_filename is provided, application/octet-stream - will be used. + The filename is no longer automatically inferred from file objects. If + you want to use automatic mimetype and etag support, pass a filepath via + `filename_or_fp` or `attachment_filename`. + + .. versionchanged:: 0.12 + The `attachment_filename` is preferred over `filename` for MIME-type + detection. :param filename_or_fp: the filename of the file to send in `latin-1`. This is relative to the :attr:`~Flask.root_path` @@ -470,8 +481,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, back to the traditional method. Make sure that the file pointer is positioned at the start of data to send before calling :func:`send_file`. - :param mimetype: the mimetype of the file if provided, otherwise - auto detection happens. + :param mimetype: the mimetype of the file if provided. If a file path is + given, auto detection happens as fallback, otherwise an + error will be raised. :param as_attachment: set to ``True`` if you want to send this file with a ``Content-Disposition: attachment`` header. :param attachment_filename: the filename for the attachment if it @@ -490,26 +502,36 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, mtime = None if isinstance(filename_or_fp, string_types): filename = filename_or_fp + if not os.path.isabs(filename): + filename = os.path.join(current_app.root_path, filename) file = None + if attachment_filename is None: + attachment_filename = os.path.basename(filename) else: file = filename_or_fp - filename = getattr(file, 'name', None) + filename = None - if filename is not None: - if not os.path.isabs(filename): - filename = os.path.join(current_app.root_path, filename) - if mimetype is None and (filename or attachment_filename): - mimetype = mimetypes.guess_type(filename or attachment_filename)[0] if mimetype is None: - mimetype = 'application/octet-stream' + if attachment_filename is not None: + mimetype = mimetypes.guess_type(attachment_filename)[0] + + if mimetype is None: + if attachment_filename is not None: + raise ValueError( + 'Unable to infer MIME-type from filename {!r}, please ' + 'pass one explicitly.'.format(mimetype_filename) + ) + raise ValueError( + 'Unable to infer MIME-type because no filename is available. ' + 'Please set either `attachment_filename`, pass a filepath to ' + '`filename_or_fp` or set your own MIME-type via `mimetype`.' + ) headers = Headers() if as_attachment: if attachment_filename is None: - if filename is None: - raise TypeError('filename unavailable, required for ' - 'sending as attachment') - attachment_filename = os.path.basename(filename) + raise TypeError('filename unavailable, required for ' + 'sending as attachment') headers.add('Content-Disposition', 'attachment', filename=attachment_filename) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 610e18ea..0f9a8e27 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -384,18 +384,30 @@ class TestSendfile(object): @app.route('/') def index(): - return flask.send_file(StringIO("party like it's"), last_modified=last_modified) + return flask.send_file(StringIO("party like it's"), + last_modified=last_modified, + mimetype='text/plain') c = app.test_client() rv = c.get('/') assert rv.last_modified == last_modified + def test_send_file_object_without_mimetype(self): + app = flask.Flask(__name__) + + with app.test_request_context(): + with pytest.raises(ValueError) as excinfo: + flask.send_file(StringIO("LOL")) + + assert 'Unable to infer MIME-type' in str(excinfo) + assert 'no filename is available' in str(excinfo) + def test_send_file_object(self): app = flask.Flask(__name__) with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html'), mode='rb') as f: - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='text/html') rv.direct_passthrough = False with app.open_resource('static/index.html') as f: assert rv.data == f.read() @@ -406,17 +418,15 @@ class TestSendfile(object): with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html')) as f: - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='text/html') assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') + assert 'x-sendfile' not in rv.headers rv.close() app.use_x_sendfile = False with app.test_request_context(): f = StringIO('Test') - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='application/octet-stream') rv.direct_passthrough = False assert rv.data == b'Test' assert rv.mimetype == 'application/octet-stream' @@ -429,7 +439,7 @@ class TestSendfile(object): return getattr(self._io, name) f = PyStringIO('Test') f.name = 'test.txt' - rv = flask.send_file(f) + rv = flask.send_file(f, attachment_filename=f.name) rv.direct_passthrough = False assert rv.data == b'Test' assert rv.mimetype == 'text/plain' @@ -446,7 +456,7 @@ class TestSendfile(object): with app.test_request_context(): f = StringIO('Test') - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='text/html') assert 'x-sendfile' not in rv.headers rv.close() @@ -454,7 +464,8 @@ class TestSendfile(object): 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) + rv = flask.send_file(f, as_attachment=True, + attachment_filename='index.html') value, options = \ parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' From 098ea0c8ca3b2f3c6715b1048bec6a42db6298e1 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 27 Aug 2016 14:32:53 +0200 Subject: [PATCH 228/440] Only passthrough_errors if PROPAGATE_EXCEPTIONS See pallets/werkzeug#954 --- flask/app.py | 3 ++- flask/cli.py | 2 +- tests/test_basic.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/flask/app.py b/flask/app.py index dac7fe26..ecd94aa8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -838,7 +838,8 @@ class Flask(_PackageBoundObject): self.debug = bool(debug) options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) - options.setdefault('passthrough_errors', True) + options.setdefault('passthrough_errors', + self.config['PROPAGATE_EXCEPTIONS']) try: run_simple(host, port, self, **options) finally: diff --git a/flask/cli.py b/flask/cli.py index 9b8fa2cd..cd301d79 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -430,7 +430,7 @@ def run_command(info, host, port, reload, debugger, eager_loading, run_simple(host, port, app, use_reloader=reload, use_debugger=debugger, threaded=with_threads, - passthrough_errors=True) + passthrough_errors=app.config['PROPAGATE_EXCEPTIONS']) @click.command('shell', short_help='Runs a shell in the app context.') diff --git a/tests/test_basic.py b/tests/test_basic.py index 55687359..2af7a514 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1268,8 +1268,8 @@ def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger, monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) - # make sure werkzeug always passes errors through - assert rv['passthrough_errors'] + # make sure werkzeug passes errors through if PROPAGATE_EXCEPTIONS + assert rv['passthrough_errors'] == propagate_exceptions def test_max_content_length(): From 3d856e03fcfb20167f24fb5a849d075e3a83126a Mon Sep 17 00:00:00 2001 From: dawran6 Date: Sun, 28 Aug 2016 15:06:53 -0700 Subject: [PATCH 229/440] sessions documentation (client side vs server side) #434 (#1888) Mention the existence of Flask extentions that handle server-side sessions. Attempt to improve the reading flow. --- docs/quickstart.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 433e4e08..c822db26 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -817,6 +817,9 @@ values do not persist across requests, cookies are indeed enabled, and you are not getting a clear error message, check the size of the cookie in your page responses compared to the size supported by web browsers. +Besides the default client-side based sessions, if you want to handle +sessions on the server-side instead, there are several +Flask extensions that support this. Message Flashing ---------------- From 6e6c3a4636387892e3725f1ba13457966c188b87 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Mon, 29 Aug 2016 18:26:20 -0400 Subject: [PATCH 230/440] Spelling (#1998) * spelling: cacheability * spelling: conceptually * spelling: javascript * spelling: reset * spelling: raised * comma: instead..., they... --- CHANGES | 2 +- docs/config.rst | 2 +- docs/errorhandling.rst | 2 +- docs/tutorial/folders.rst | 2 +- flask/app.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 5273e09d..4ea6323b 100644 --- a/CHANGES +++ b/CHANGES @@ -327,7 +327,7 @@ Released on September 29th 2011, codename Rakija - Applications now not only have a root path where the resources and modules are located but also an instance path which is the designated place to drop files that are modified at runtime (uploads etc.). Also this is - conceptionally only instance depending and outside version control so it's + conceptually only instance depending and outside version control so it's the perfect place to put configuration files etc. For more information see :ref:`instance-folders`. - Added the ``APPLICATION_ROOT`` configuration variable. diff --git a/docs/config.rst b/docs/config.rst index 4958d471..89fa0924 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -177,7 +177,7 @@ The following configuration values are used internally by Flask: behavior by changing this variable. This is not recommended but might give you a performance improvement on the - cost of cachability. + cost of cacheability. ``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) jsonify responses will be pretty printed if they are not requested by an diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 2dc7fafe..a4d5f781 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -77,7 +77,7 @@ You might want to show custom error pages to the user when an error occurs. This can be done by registering error handlers. Error handlers are normal :ref:`views` but instead of being registered for -routes they are registered for exceptions that are rised while trying to +routes, they are registered for exceptions that are raised while trying to do something else. Registering diff --git a/docs/tutorial/folders.rst b/docs/tutorial/folders.rst index 4e117d1f..ba62b3b7 100644 --- a/docs/tutorial/folders.rst +++ b/docs/tutorial/folders.rst @@ -19,7 +19,7 @@ creating the database schema as well as the main module. As a quick side note, the files inside of the :file:`static` folder are available to users of the application via HTTP. This is the place where CSS and -Javascript files go. Inside the :file:`templates` folder, Flask will look for +JavaScript files go. Inside the :file:`templates` folder, Flask will look for `Jinja2`_ templates. You will see examples of this later on. For now you should continue with :ref:`tutorial-schema`. diff --git a/flask/app.py b/flask/app.py index ecd94aa8..774d5234 100644 --- a/flask/app.py +++ b/flask/app.py @@ -844,7 +844,7 @@ class Flask(_PackageBoundObject): run_simple(host, port, self, **options) finally: # reset the first request information if the development server - # resetted normally. This makes it possible to restart the server + # reset normally. This makes it possible to restart the server # without reloader and that stuff from an interactive shell. self._got_first_request = False From b42e43e3b622af1b8c04a8298e26817343f5e4a1 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Wed, 31 Aug 2016 12:37:36 -0400 Subject: [PATCH 231/440] Better workflow for flaskr and other basic apps (#2000) - adds `from flaskr import app` to top-level in flaskr module - effect is that `export FLASK_APP=flaskr` works over the more verbose `export FLASK_APP=flaskr.flask` - see the readme for how to run - all tests are passing with `py.test` or `python setup.py test` (in venv) --- examples/flaskr/README | 2 +- examples/flaskr/flaskr/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/flaskr/README b/examples/flaskr/README index 3cb021e7..90860ff2 100644 --- a/examples/flaskr/README +++ b/examples/flaskr/README @@ -19,7 +19,7 @@ 3. Instruct flask to use the right application - export FLASK_APP=flaskr.flaskr + export FLASK_APP=flaskr 4. initialize the database with this command: diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py index e69de29b..14a36539 100644 --- a/examples/flaskr/flaskr/__init__.py +++ b/examples/flaskr/flaskr/__init__.py @@ -0,0 +1 @@ +from flaskr import app \ No newline at end of file From 96b6345c1a187e3ae78e0e4faa4a1afc1a8cc69f Mon Sep 17 00:00:00 2001 From: PHeanEX Date: Wed, 31 Aug 2016 22:05:12 +0200 Subject: [PATCH 232/440] Fix small grammar error (Of/Or) (#2001) --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index a4d5f781..3bda5f15 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -51,7 +51,7 @@ And then add this to your Flask app:: from raven.contrib.flask import Sentry sentry = Sentry(app, dsn='YOUR_DSN_HERE') -Of if you are using factories you can also init it later:: +Or if you are using factories you can also init it later:: from raven.contrib.flask import Sentry sentry = Sentry(dsn='YOUR_DSN_HERE') From 92ce20eeacf5de24803abaf70a3658806fa4d74f Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 5 Sep 2016 03:28:05 +0400 Subject: [PATCH 233/440] Fix error in send_file helper (#2003) * Fix error in send_file (mimetype_filename is not defined) * fix formatting error message in send_file --- flask/helpers.py | 4 ++-- tests/test_helpers.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index e8422f7a..56a91dd8 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -518,8 +518,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if mimetype is None: if attachment_filename is not None: raise ValueError( - 'Unable to infer MIME-type from filename {!r}, please ' - 'pass one explicitly.'.format(mimetype_filename) + 'Unable to infer MIME-type from filename {0!r}, please ' + 'pass one explicitly.'.format(attachment_filename) ) raise ValueError( 'Unable to infer MIME-type because no filename is available. ' diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 0f9a8e27..fc85625e 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -398,10 +398,14 @@ class TestSendfile(object): with app.test_request_context(): with pytest.raises(ValueError) as excinfo: flask.send_file(StringIO("LOL")) - assert 'Unable to infer MIME-type' in str(excinfo) assert 'no filename is available' in str(excinfo) + with app.test_request_context(): + with pytest.raises(ValueError) as excinfo: + flask.send_file(StringIO("LOL"), attachment_filename='filename') + assert "Unable to infer MIME-type from filename 'filename'" in str(excinfo) + def test_send_file_object(self): app = flask.Flask(__name__) From ccd02bfe8c90ee8dbf30cc2be61a7d483ff24cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afonso=20Queir=C3=B3s?= Date: Mon, 5 Sep 2016 16:57:00 +0200 Subject: [PATCH 234/440] Correcting Custom Test Client class docs (#2004) --- flask/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 774d5234..bfc0efc2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -878,9 +878,9 @@ class Flask(_PackageBoundObject): from flask.testing import FlaskClient class CustomClient(FlaskClient): - def __init__(self, authentication=None, *args, **kwargs): - FlaskClient.__init__(*args, **kwargs) - self._authentication = authentication + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) app.test_client_class = CustomClient client = app.test_client(authentication='Basic ....') From c4ec6954e57b1ab3cb5a0d178e6030f8bab6c149 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 6 Sep 2016 22:32:34 +0200 Subject: [PATCH 235/440] Don't passthrough_errors unless instructed. (#2006) Fix #2005 Revert #1679 and #1996 --- flask/app.py | 2 -- flask/cli.py | 3 +-- tests/test_basic.py | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/flask/app.py b/flask/app.py index bfc0efc2..deedc83a 100644 --- a/flask/app.py +++ b/flask/app.py @@ -838,8 +838,6 @@ class Flask(_PackageBoundObject): self.debug = bool(debug) options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) - options.setdefault('passthrough_errors', - self.config['PROPAGATE_EXCEPTIONS']) try: run_simple(host, port, self, **options) finally: diff --git a/flask/cli.py b/flask/cli.py index cd301d79..28818515 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -429,8 +429,7 @@ def run_command(info, host, port, reload, debugger, eager_loading, print(' * Forcing debug mode %s' % (debug and 'on' or 'off')) run_simple(host, port, app, use_reloader=reload, - use_debugger=debugger, threaded=with_threads, - passthrough_errors=app.config['PROPAGATE_EXCEPTIONS']) + use_debugger=debugger, threaded=with_threads) @click.command('shell', short_help='Runs a shell in the app context.') diff --git a/tests/test_basic.py b/tests/test_basic.py index 2af7a514..33f6ec07 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1268,8 +1268,6 @@ def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger, monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) - # make sure werkzeug passes errors through if PROPAGATE_EXCEPTIONS - assert rv['passthrough_errors'] == propagate_exceptions def test_max_content_length(): From dbcd64e2ee4c21775a8b06b467e2d2aeae76c9d4 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 7 Sep 2016 18:18:53 +0200 Subject: [PATCH 236/440] Changelog for #2006 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 4ea6323b..76907082 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Version 0.12 ``application/octet-stream``. See pull request ``#1988``. - Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` (pull request ``#1730``). +- Revert a behavior change that made the dev server crash instead of returning + a Internal Server Error (pull request ``#2006``). Version 0.11.2 -------------- From 9cd32cac32433d7ea03cbd64de6ab5c8ea64a981 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 8 Sep 2016 11:55:59 +0300 Subject: [PATCH 237/440] Corrected after response for error handlers Before this change after request functions were not correctly invoked for error handlers. --- CHANGES | 2 ++ flask/app.py | 29 +++++++++++++++++++++++++---- tests/test_basic.py | 23 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 76907082..ac53fbd5 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,8 @@ Version 0.12 (pull request ``#1730``). - Revert a behavior change that made the dev server crash instead of returning a Internal Server Error (pull request ``#2006``). +- Correctly invoke response handlers for both regular request dispatching as + well as error handlers. Version 0.11.2 -------------- diff --git a/flask/app.py b/flask/app.py index deedc83a..40fedda6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1555,7 +1555,7 @@ class Flask(_PackageBoundObject): self.log_exception((exc_type, exc_value, tb)) if handler is None: return InternalServerError() - return handler(e) + return self.finalize_request(handler(e), from_error_handler=True) def log_exception(self, exc_info): """Logs an exception. This is called by :meth:`handle_exception` @@ -1623,9 +1623,30 @@ class Flask(_PackageBoundObject): rv = self.dispatch_request() except Exception as e: rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request(self, rv, from_error_handler=False): + """Given the return value from a view function this finalizes + the request by converting it into a repsonse and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled failures in + response processing will be logged and otherwise ignored. + + :internal: + """ response = self.make_response(rv) - response = self.process_response(response) - request_finished.send(self, response=response) + try: + response = self.process_response(response) + request_finished.send(self, response=response) + except Exception: + if not from_error_handler: + raise + self.logger.exception('Request finalizing failed with an ' + 'error while handling an error') return response def try_trigger_before_first_request_functions(self): @@ -1972,7 +1993,7 @@ class Flask(_PackageBoundObject): response = self.full_dispatch_request() except Exception as e: error = e - response = self.make_response(self.handle_exception(e)) + response = self.handle_exception(e) return response(environ, start_response) finally: if self.should_ignore_error(error): diff --git a/tests/test_basic.py b/tests/test_basic.py index 33f6ec07..be3d5edd 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -768,6 +768,29 @@ def test_error_handling(): assert b'forbidden' == rv.data +def test_error_handling_processing(): + app = flask.Flask(__name__) + app.config['LOGGER_HANDLER_POLICY'] = 'never' + + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + + @app.route('/') + def broken_func(): + 1 // 0 + + @app.after_request + def after_request(resp): + resp.mimetype = 'text/x-special' + return resp + + with app.test_client() as c: + resp = c.get('/') + assert resp.mimetype == 'text/x-special' + assert resp.data == b'internal server error' + + def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From e6f9d2b41417b442946acfede9200d361ac401cc Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Thu, 8 Sep 2016 09:19:48 -0400 Subject: [PATCH 238/440] Clean up tutorial docs for installable app pattern with flaskr (#2002) * Clean up tutorial docs for installable app pattern - reading sequentially through the tutorial works. - fixes references to `export FLASK_APP=flaskr.flaskr` * Fixes titles for each section of flaskr tutorial * Revert grammar * Emphasize the Packaging Guide - adds more general packaging resource - removes the emphasis put on setuptools * rephrase and remove note admonitions - expanded on few points - removed note blocks, they are unneccessary * Remove note about reinstalling to update cli - I had mistakenly thought it was necessary to re-install the app to update the cli. - the `--editable` flag detects the change and the cli updates without issue. --- docs/tutorial/css.rst | 2 +- docs/tutorial/dbcon.rst | 2 +- docs/tutorial/index.rst | 2 +- .../{setuptools.rst => packaging.rst} | 47 ++++++++++++------ docs/tutorial/setup.rst | 2 +- docs/tutorial/templates.rst | 2 +- docs/tutorial/testing.rst | 48 +++++++++++-------- docs/tutorial/views.rst | 2 +- 8 files changed, 67 insertions(+), 40 deletions(-) rename docs/tutorial/{setuptools.rst => packaging.rst} (61%) diff --git a/docs/tutorial/css.rst b/docs/tutorial/css.rst index ea461a89..56414657 100644 --- a/docs/tutorial/css.rst +++ b/docs/tutorial/css.rst @@ -1,6 +1,6 @@ .. _tutorial-css: -Step 9: Adding Style +Step 8: Adding Style ==================== Now that everything else works, it's time to add some style to the diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 9f4428b9..2dd3d7be 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -3,7 +3,7 @@ Step 4: Database Connections ---------------------------- -You now have a function for establishing a database connection with +You currently have a function for establishing a database connection with `connect_db`, but by itself, it is not particularly useful. Creating and closing database connections all the time is very inefficient, so you will need to keep it around for longer. Because database connections diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index ccd4e7d2..f0a583e0 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -24,7 +24,7 @@ the `example source`_. folders schema setup - setuptools + packaging dbcon dbinit views diff --git a/docs/tutorial/setuptools.rst b/docs/tutorial/packaging.rst similarity index 61% rename from docs/tutorial/setuptools.rst rename to docs/tutorial/packaging.rst index 306d94d3..8db6531e 100644 --- a/docs/tutorial/setuptools.rst +++ b/docs/tutorial/packaging.rst @@ -1,7 +1,7 @@ -.. _tutorial-setuptools: +.. _tutorial-packaging: -Step 3: Installing flaskr with setuptools -========================================= +Step 3: Installing flaskr as a Package +====================================== Flask is now shipped with built-in support for `Click`_. Click provides Flask with enhanced and extensible command line utilities. Later in this @@ -9,17 +9,21 @@ tutorial you will see exactly how to extend the ``flask`` command line interface (CLI). A useful pattern to manage a Flask application is to install your app -using `setuptools`_. This involves creating a :file:`setup.py` -in the projects root directory. You also need to add an empty -:file:`__init__.py` file to make the :file:`flaskr/flaskr` directory -a package. The code structure at this point should be:: +following the `Python Packaging Guide`_. Presently this involves +creating two new files; :file:`setup.py` and :file:`MANIFEST.in` in the +projects root directory. You also need to add an :file:`__init__.py` +file to make the :file:`flaskr/flaskr` directory a package. After these +changes, your code structure should be:: /flaskr /flaskr __init__.py /static /templates + flaskr.py + schema.sql setup.py + MANIFEST.in The content of the ``setup.py`` file for ``flaskr`` is: @@ -46,22 +50,37 @@ following lines:: graft flaskr/static include flaskr/schema.sql +To simplify locating the application, add the following import statement +into this file, :file:`flaskr/__init__.py`: + +.. sourcecode:: python + + from flaskr import app + +This import statement brings the application instance into the top-level +of the application package. When it is time to run the application, the +Flask development server needs the location of the app instance. This +import statement simplifies the location process. Without it the export +statement a few steps below would need to be +``export FLASK_APP=flaskr.flaskr``. + At this point you should be able to install the application. As usual, it is recommended to install your Flask application within a `virtualenv`_. With that said, go ahead and install the application with:: pip install --editable . -.. note:: The above installation command assumes that it is run within the - projects root directory, `flaskr/`. Also, the `editable` flag allows - editing source code without having to reinstall the Flask app each time - you make changes. +The above installation command assumes that it is run within the projects +root directory, `flaskr/`. The `editable` flag allows editing +source code without having to reinstall the Flask app each time you make +changes. The flaskr app is now installed in your virtualenv (see output +of ``pip freeze``). With that out of the way, you should be able to start up the application. Do this with the following commands:: - export FLASK_APP=flaskr.flaskr - export FLASK_DEBUG=1 + export FLASK_APP=flaskr + export FLASK_DEBUG=true flask run (In case you are on Windows you need to use `set` instead of `export`). @@ -85,5 +104,5 @@ but first, you should get the database working. Continue with :ref:`tutorial-dbcon`. .. _Click: http://click.pocoo.org -.. _setuptools: https://setuptools.readthedocs.io +.. _Python Packaging Guide: https://packaging.python.org .. _virtualenv: https://virtualenv.pypa.io diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index 78b6390a..4bedb54c 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -94,4 +94,4 @@ tuples. In the next section you will see how to run the application. -Continue with :ref:`tutorial-setuptools`. +Continue with :ref:`tutorial-packaging`. diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index d6558233..269e8df1 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -1,6 +1,6 @@ .. _tutorial-templates: -Step 8: The Templates +Step 7: The Templates ===================== Now it is time to start working on the templates. As you may have diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index c5ecf7dd..dcf36594 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -9,10 +9,10 @@ modifications in the future. The application above is used as a basic example of how to perform unit testing in the :ref:`testing` section of the documentation. Go there to see how easy it is to test Flask applications. -Adding Tests to flaskr -====================== +Adding tests to flaskr +---------------------- -Assuming you have seen the testing section above and have either written +Assuming you have seen the :ref:`testing` section and have either written your own tests for ``flaskr`` or have followed along with the examples provided, you might be wondering about ways to organize the project. @@ -24,30 +24,38 @@ One possible and recommended project structure is:: static/ templates/ tests/ - context.py test_flaskr.py setup.py MANIFEST.in -For now go ahead a create the :file:`tests/` directory as well as the -:file:`context.py` and :file:`test_flaskr.py` files, if you haven't -already. The context file is used as an import helper. The contents -of that file are:: +For now go ahead a create the :file:`tests/` directory as well as the +:file:`test_flaskr.py` file. - import sys, os +Running the tests +----------------- - basedir = os.path.dirname(os.path.abspath(__file__)) - sys.path.insert(0, basedir + '/../') +At this point you can run the tests. Here ``pytest`` will be used. - from flaskr import flaskr +.. note:: Make sure that ``pytest`` is installed in the same virtualenv + as flaskr. Otherwise ``pytest`` test will not be able to import the + required components to test the application:: -Testing + Setuptools -==================== + pip install -e . + pip install pytest -One way to handle testing is to integrate it with ``setuptools``. All it -requires is adding a couple of lines to the :file:`setup.py` file and -creating a new file :file:`setup.cfg`. Go ahead and update the -:file:`setup.py` to contain:: +Run and watch the tests pass, within the top-level :file:`flaskr/` +directory as:: + + py.test + +Testing + setuptools +-------------------- + +One way to handle testing is to integrate it with ``setuptools``. Here +that requires adding a couple of lines to the :file:`setup.py` file and +creating a new file :file:`setup.cfg`. One benefit of running the tests +this way is that you do not have to install ``pytest``. Go ahead and +update the :file:`setup.py` file to contain:: from setuptools import setup @@ -58,7 +66,6 @@ creating a new file :file:`setup.cfg`. Go ahead and update the install_requires=[ 'flask', ], - ) setup_requires=[ 'pytest-runner', ], @@ -66,6 +73,7 @@ creating a new file :file:`setup.cfg`. Go ahead and update the 'pytest', ], ) + Now create :file:`setup.cfg` in the project root (alongside :file:`setup.py`):: @@ -85,4 +93,4 @@ found, run, and hopefully pass. This is one possible way to run and manage testing. Here ``pytest`` is used, but there are other options such as ``nose``. Integrating testing with ``setuptools`` is convenient because it is not necessary to actually -download ``pytest`` or any other testing framework one might use. \ No newline at end of file +download ``pytest`` or any other testing framework one might use. diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index d9838073..4364d973 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -1,6 +1,6 @@ .. _tutorial-views: -Step 7: The View Functions +Step 6: The View Functions ========================== Now that the database connections are working, you can start writing the From c687ffb1921ce755eb32e105c4f689c0cb4afd81 Mon Sep 17 00:00:00 2001 From: Akbar Ibrahim Date: Thu, 8 Sep 2016 21:04:51 +0530 Subject: [PATCH 239/440] Fixed error in errorhandler doc string. (#2014) --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index deedc83a..9d14bbe3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1115,7 +1115,7 @@ class Flask(_PackageBoundObject): @setupmethod def errorhandler(self, code_or_exception): - """A decorator that is used to register a function give a given + """A decorator that is used to register a function given an error code. Example:: @app.errorhandler(404) From c54c538c1148c07df808acf701776f6db180302f Mon Sep 17 00:00:00 2001 From: Andrew Arendt Date: Thu, 8 Sep 2016 12:24:07 -0500 Subject: [PATCH 240/440] fixed deprecated syntax in setup.cfg (#2015) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 47c225ea..34414b3e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,5 +4,5 @@ release = egg_info -RDb '' [wheel] universal = 1 -[pytest] +[tool:pytest] norecursedirs = .* *.egg *.egg-info env* artwork docs examples From a30951ec28e23a5d1c9380c4bd5844f2290c1dc8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 10 Sep 2016 03:33:53 +0300 Subject: [PATCH 241/440] Do not error for unknown files if send_file sends an actual file --- flask/helpers.py | 3 ++- tests/test_helpers.py | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 56a91dd8..4034112e 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -513,7 +513,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if mimetype is None: if attachment_filename is not None: - mimetype = mimetypes.guess_type(attachment_filename)[0] + mimetype = mimetypes.guess_type(attachment_filename)[0] \ + or 'application/octet-stream' if mimetype is None: if attachment_filename is not None: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index fc85625e..e1469007 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -402,9 +402,7 @@ class TestSendfile(object): assert 'no filename is available' in str(excinfo) with app.test_request_context(): - with pytest.raises(ValueError) as excinfo: - flask.send_file(StringIO("LOL"), attachment_filename='filename') - assert "Unable to infer MIME-type from filename 'filename'" in str(excinfo) + flask.send_file(StringIO("LOL"), attachment_filename='filename') def test_send_file_object(self): app = flask.Flask(__name__) From 1f0ca894a27f12131079c27eccdcc3fc9283eadd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 11 Sep 2016 16:57:43 +0300 Subject: [PATCH 242/440] Killed now dead code --- flask/helpers.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 4034112e..05361626 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -517,11 +517,6 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, or 'application/octet-stream' if mimetype is None: - if attachment_filename is not None: - raise ValueError( - 'Unable to infer MIME-type from filename {0!r}, please ' - 'pass one explicitly.'.format(attachment_filename) - ) raise ValueError( 'Unable to infer MIME-type because no filename is available. ' 'Please set either `attachment_filename`, pass a filepath to ' From 59104db2f2f0a507aea2625230e446361e2fc174 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Sun, 11 Sep 2016 11:53:35 -0400 Subject: [PATCH 243/440] Address #1980 (#2021) * Distinguish between directories and files * Convert larger apps to make use of setup.py - replaces runserver.py with setup.py - example now runs with recommended structure * Fixes a typo and formats the added paragraph --- docs/patterns/packages.rst | 47 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index af51717d..1cd77974 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -8,9 +8,9 @@ module. That is quite simple. Imagine a small application looks like this:: /yourapplication - /yourapplication.py + yourapplication.py /static - /style.css + style.css /templates layout.html index.html @@ -29,9 +29,9 @@ You should then end up with something like that:: /yourapplication /yourapplication - /__init__.py + __init__.py /static - /style.css + style.css /templates layout.html index.html @@ -41,11 +41,36 @@ You should then end up with something like that:: But how do you run your application now? The naive ``python yourapplication/__init__.py`` will not work. Let's just say that Python does not want modules in packages to be the startup file. But that is not -a big problem, just add a new file called :file:`runserver.py` next to the inner +a big problem, just add a new file called :file:`setup.py` next to the inner :file:`yourapplication` folder with the following contents:: - from yourapplication import app - app.run(debug=True) + from setuptools import setup + + setup( + name='yourapplication', + packages=['yourapplication'], + include_package_data=True, + install_requires=[ + 'flask', + ], + ) + +In order to run the application you need to export an environment variable +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. Similiarly you can turn on "debug +mode" with this environment variable:: + + export FLASK_DEBUG=true + +In order to install and run the application you need to issue the following +commands:: + + pip install -e . + flask run What did we gain from this? Now we can restructure the application a bit into multiple modules. The only thing you have to remember is the @@ -77,12 +102,12 @@ And this is what :file:`views.py` would look like:: You should then end up with something like that:: /yourapplication - /runserver.py + setup.py /yourapplication - /__init__.py - /views.py + __init__.py + views.py /static - /style.css + style.css /templates layout.html index.html From 11f3a3f6ddd5c6bc2b6223eb2ad7cf25e0010132 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 11 Sep 2016 21:28:30 +0300 Subject: [PATCH 244/440] Updated upgrade docs --- docs/upgrading.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 6b933c59..41b70f03 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -54,8 +54,8 @@ misleading ``name`` attribute. Silently swallowing errors in such cases was not a satisfying solution. Additionally the default of falling back to ``application/octet-stream`` has -been removed. If Flask can't guess one or the user didn't provide one, the -function fails. +been restricted. If Flask can't guess one or the user didn't provide one, the +function fails if no filename information was provided. .. _upgrading-to-011: From 2b4761599873e08a21d835427269a84def0d4ea6 Mon Sep 17 00:00:00 2001 From: Pablo Marti Date: Mon, 12 Sep 2016 08:41:09 +0100 Subject: [PATCH 245/440] Fix typo in docs Also added one missing comma for readability --- flask/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index ee8a3fba..90aedf1d 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1627,13 +1627,13 @@ class Flask(_PackageBoundObject): def finalize_request(self, rv, from_error_handler=False): """Given the return value from a view function this finalizes - the request by converting it into a repsonse and invoking the + the request by converting it into a response and invoking the postprocessing functions. This is invoked for both normal request dispatching as well as error handlers. Because this means that it might be called as a result of a failure a special safe mode is available which can be enabled - with the `from_error_handler` flag. If enabled failures in + with the `from_error_handler` flag. If enabled, failures in response processing will be logged and otherwise ignored. :internal: From 0664a3f2ddfc227797b5a8f0654e9959b15d9e64 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 12 Sep 2016 21:55:17 +0300 Subject: [PATCH 246/440] Set merge strategy for CHANGES --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8383fff9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGES merge=union From 270355abdcaa8f20ad6e7dd39f69279859a7055c Mon Sep 17 00:00:00 2001 From: Andrew Arendt Date: Wed, 14 Sep 2016 13:03:21 -0500 Subject: [PATCH 247/440] Remove nonsense from cli docs --- docs/cli.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 10f5b34c..7ddf50f3 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -61,9 +61,7 @@ Debug Flag The :command:`flask` script can also be instructed to enable the debug mode of the application automatically by exporting ``FLASK_DEBUG``. If -set to ``1`` debug is enabled or ``0`` disables it. - -Or with a filename:: +set to ``1`` debug is enabled or ``0`` disables it:: export FLASK_DEBUG=1 From 09fec941f4a1a69d6e1ad793a6a9c0e3df17d8d1 Mon Sep 17 00:00:00 2001 From: Bruno Thalmann Date: Sun, 18 Sep 2016 14:10:00 +0200 Subject: [PATCH 248/440] Removed unused import. (#2026) --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 05361626..1febbc5c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -39,7 +39,7 @@ 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, PY2 +from ._compat import string_types, text_type # sentinel From dcfdfc6476b7cce7c32f48e90e6558cdd2c854fb Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 18 Sep 2016 15:47:52 +0200 Subject: [PATCH 249/440] Use abort docs from Werkzeug Fix #1960 --- docs/api.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e72c9ace..d77da3de 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -316,13 +316,7 @@ Useful Functions and Classes .. autofunction:: url_for -.. function:: abort(code) - - Raises an :exc:`~werkzeug.exceptions.HTTPException` for the given - status code. For example to abort request handling with a page not - found exception, you would call ``abort(404)``. - - :param code: the HTTP error code. +.. autofunction:: abort .. autofunction:: redirect From dbeed240674ed7a7160ae82a603eaa930dd1fd00 Mon Sep 17 00:00:00 2001 From: Benjamin Dopplinger Date: Mon, 19 Sep 2016 13:24:46 +1000 Subject: [PATCH 250/440] Fix typo in MethodView doc (#2028) --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 6e249180..83394c1f 100644 --- a/flask/views.py +++ b/flask/views.py @@ -123,7 +123,7 @@ class MethodViewType(type): class MethodView(with_metaclass(MethodViewType, View)): """Like a regular class-based view but that dispatches requests to particular methods. For instance if you implement a method called - :meth:`get` it means you will response to ``'GET'`` requests and + :meth:`get` it means it will respond to ``'GET'`` requests and the :meth:`dispatch_request` implementation will automatically forward your request to that. Also :attr:`options` is set for you automatically:: From 01081dbe6cdfa3fc43d8e1fff708d4ed95e1be7e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 19 Sep 2016 23:29:52 +0200 Subject: [PATCH 251/440] Avoid always-false statement See https://github.com/pallets/flask/pull/1849/files#r79371299 --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 1febbc5c..fd072ba1 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -559,7 +559,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv.cache_control.max_age = cache_timeout rv.expires = int(time() + cache_timeout) - if add_etags and filename is not None and file is None: + if add_etags and filename is not None: from warnings import warn try: From a6a36ec72a1f65514e137b0f21708e8c61dbe4ba Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 24 Sep 2016 04:07:19 -0700 Subject: [PATCH 252/440] Updated mod_wsgi.rst to point to new mod_wsgi repo (#2038) --- docs/deploying/mod_wsgi.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index b06a1904..0f4af6c3 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -130,12 +130,12 @@ to httpd 2.4 syntax Require all granted -For more information consult the `mod_wsgi wiki`_. +For more information consult the `mod_wsgi documentation`_. -.. _mod_wsgi: http://code.google.com/p/modwsgi/ -.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide +.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi +.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html .. _virtual python: https://pypi.python.org/pypi/virtualenv -.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/w/list +.. _mod_wsgi documentation: http://modwsgi.readthedocs.io/en/develop/index.html Troubleshooting --------------- From f3d661de6676125bc765e286c5dd89e3e10ad82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=98=8E?= Date: Mon, 26 Sep 2016 00:25:54 +0800 Subject: [PATCH 253/440] Fix unbound error (#2039) --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 90aedf1d..59c77a15 100644 --- a/flask/app.py +++ b/flask/app.py @@ -519,7 +519,7 @@ class Flask(_PackageBoundObject): #: def to_python(self, value): #: return value.split(',') #: def to_url(self, values): - #: return ','.join(BaseConverter.to_url(value) + #: return ','.join(super(ListConverter, self).to_url(value) #: for value in values) #: #: app = Flask(__name__) From 7186a5aaf571e1d08a617138867998cbea518522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Charles?= Date: Mon, 26 Sep 2016 12:43:46 +0200 Subject: [PATCH 254/440] make use of range requests if available in werkzeug (#2031) * make use of range requests if available in werkzeug * different logic for testing werkzeug functionality --- CHANGES | 1 + flask/helpers.py | 36 ++++++++++++++++++------ tests/test_helpers.py | 65 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index cec86584..5a1f5a33 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Version 0.12 - Correctly invoke response handlers for both regular request dispatching as well as error handlers. - Disable logger propagation by default for the app logger. +- Add support for range requests in ``send_file``. Version 0.11.2 -------------- diff --git a/flask/helpers.py b/flask/helpers.py index fd072ba1..c6c2cddc 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -25,8 +25,9 @@ try: except ImportError: from urlparse import quote as url_quote -from werkzeug.datastructures import Headers -from werkzeug.exceptions import BadRequest, NotFound +from werkzeug.datastructures import Headers, Range +from werkzeug.exceptions import BadRequest, NotFound, \ + RequestedRangeNotSatisfiable # this was moved in 0.7 try: @@ -446,6 +447,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, ETags will also be attached automatically if a `filename` is provided. You can turn this off by setting `add_etags=False`. + If `conditional=True` and `filename` is provided, this method will try to + upgrade the response stream to support range requests. This will allow + the request to be answered with partial content response. + Please never pass filenames to this function from user sources; you should use :func:`send_from_directory` instead. @@ -500,6 +505,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, If a file was passed, this overrides its mtime. """ mtime = None + fsize = None if isinstance(filename_or_fp, string_types): filename = filename_or_fp if not os.path.isabs(filename): @@ -535,13 +541,15 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if file is not None: file.close() headers['X-Sendfile'] = filename - headers['Content-Length'] = os.path.getsize(filename) + fsize = os.path.getsize(filename) + headers['Content-Length'] = fsize data = None else: if file is None: file = open(filename, 'rb') mtime = os.path.getmtime(filename) - headers['Content-Length'] = os.path.getsize(filename) + fsize = os.path.getsize(filename) + headers['Content-Length'] = fsize data = wrap_file(request.environ, file) rv = current_app.response_class(data, mimetype=mimetype, headers=headers, @@ -575,12 +583,22 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, warn('Access %s failed, maybe it does not exist, so ignore etags in ' 'headers' % filename, stacklevel=2) - if conditional: + if conditional: + if callable(getattr(Range, 'to_content_range_header', None)): + # Werkzeug supports Range Requests + # Remove this test when support for Werkzeug <0.12 is dropped + try: + rv = rv.make_conditional(request, accept_ranges=True, + complete_length=fsize) + except RequestedRangeNotSatisfiable: + file.close() + raise + else: rv = rv.make_conditional(request) - # make sure we don't send x-sendfile for servers that - # ignore the 304 status code for x-sendfile. - if rv.status_code == 304: - rv.headers.pop('x-sendfile', None) + # make sure we don't send x-sendfile for servers that + # ignore the 304 status code for x-sendfile. + if rv.status_code == 304: + rv.headers.pop('x-sendfile', None) return rv diff --git a/tests/test_helpers.py b/tests/test_helpers.py index e1469007..8348331b 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -14,8 +14,10 @@ import pytest import os import uuid import datetime + import flask from logging import StreamHandler +from werkzeug.datastructures import Range from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date @@ -462,6 +464,69 @@ class TestSendfile(object): assert 'x-sendfile' not in rv.headers rv.close() + @pytest.mark.skipif( + not callable(getattr(Range, 'to_content_range_header', None)), + reason="not implement within werkzeug" + ) + def test_send_file_range_request(self): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return flask.send_file('static/index.html', conditional=True) + + c = app.test_client() + + rv = c.get('/', headers={'Range': 'bytes=4-15'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[4:16] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=4-'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[4:] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=4-1000'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[4:] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=-10'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[-10:] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=1000-'}) + assert rv.status_code == 416 + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=-'}) + assert rv.status_code == 416 + rv.close() + + rv = c.get('/', headers={'Range': 'somethingsomething'}) + assert rv.status_code == 416 + rv.close() + + last_modified = datetime.datetime.fromtimestamp(os.path.getmtime( + os.path.join(app.root_path, 'static/index.html'))).replace( + microsecond=0) + + rv = c.get('/', headers={'Range': 'bytes=4-15', + 'If-Range': http_date(last_modified)}) + assert rv.status_code == 206 + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=4-15', 'If-Range': http_date( + datetime.datetime(1999, 1, 1))}) + assert rv.status_code == 200 + rv.close() + def test_attachment(self): app = flask.Flask(__name__) with app.test_request_context(): From 49ecc88d9949bed6297944cb4a3d115fa9727aab Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Sat, 1 Oct 2016 12:45:22 -0400 Subject: [PATCH 255/440] Remove `-a/--app` from Quickstart documentation (#2046) * Remove `-a/--app` from Quickstart documentation As mentioned in #2009, simplifying the CLI saw the removal of the `-a/--app` flag. Therefore, the only way to specify the module to import is by setting `FLASK_APP`. * Remove misleading `either` from CLI help The CLI help details how to run the application, but still uses the phrasing "either through the `FLASK_APP`...". This likely is an artifact from when `-a/--app` was still present in the CLI. --- docs/quickstart.rst | 8 ++++---- flask/cli.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c822db26..b444e080 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -102,10 +102,10 @@ docs to see the alternative method for running a server. Invalid Import Name ``````````````````` -The ``-a`` argument to :command:`flask` is the name of the module to -import. In case that module is incorrectly named you will get an import -error upon start (or if debug is enabled when you navigate to the -application). It will tell you what it tried to import and why it failed. +The ``FLASK_APP`` environment variable is the name of the module to import at +:command:`flask run`. In case that module is incorrectly named you will get an +import error upon start (or if debug is enabled when you navigate to the +application). It will tell you what it tried to import and why it failed. The most common reason is a typo or because you did not actually create an ``app`` object. diff --git a/flask/cli.py b/flask/cli.py index 28818515..6c8cf32d 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -469,7 +469,7 @@ def shell_command(): cli = FlaskGroup(help="""\ This shell command acts as general utility script for Flask applications. -It loads the application configured (either through the FLASK_APP environment +It loads the application configured (through the FLASK_APP environment variable) and then provides commands either provided by the application or Flask itself. From cd13a5cf6215914ba4c3e0963d2189fe19fed508 Mon Sep 17 00:00:00 2001 From: Hassam Date: Sat, 8 Oct 2016 13:34:56 -0500 Subject: [PATCH 256/440] Fix #2051: Fix flaskr import in flaskr/__init__.py (#2052) --- examples/flaskr/flaskr/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py index 14a36539..37a7b5cc 100644 --- a/examples/flaskr/flaskr/__init__.py +++ b/examples/flaskr/flaskr/__init__.py @@ -1 +1 @@ -from flaskr import app \ No newline at end of file +from flaskr.flaskr import app \ No newline at end of file From 2b03eca1b791c1f88a46b42c83b62214eb0f04c2 Mon Sep 17 00:00:00 2001 From: Geoffrey Bauduin Date: Tue, 11 Oct 2016 15:27:48 +0200 Subject: [PATCH 257/440] Updated Celery pattern The given pattern caused Celery to lose the current Context --- docs/patterns/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index 673d953b..548da29b 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -44,7 +44,7 @@ This is all that is necessary to properly integrate Celery with Flask:: abstract = True def __call__(self, *args, **kwargs): with app.app_context(): - return TaskBase.__call__(self, *args, **kwargs) + return self.run(*args, **kwargs) celery.Task = ContextTask return celery From bd5e297aa9697f42a61682fe56f10eeb650e8540 Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Wed, 12 Oct 2016 02:54:25 -0400 Subject: [PATCH 258/440] Default environ (#2047) * Add init to FlaskClient This addresses #1467. The init in the subclass can now take in `environ_base`, which will then get passed to `make_test_environ_builder` and to `EnvironBuilder` via keyword args. This should provide the default environment capability on `app.test_client()` init. * Add kwarg `environ_base` to `make_test_environ_builder` call This change now passes `environ_base` from either `kwargs` in `FlaskClient.open` or `FlaskClient.environ_base` if passed into the init. * Fix assignment reference typo * Add default `environ_base` to `FlaskClient.__init__` * Set default kwargs for `environ_base` in `FlaskClient.open` * Remove specific environ_base kwarg since its in kwargs * Add docstring to FlaskClient detailing environ_base * Document app.test_client default environ in CHANGES * Re-word environ_base changes in FlaskClient docstring * Add client.environ_base tests * Mention preset default environ in `app.test_client` * Add versionchanged directive to docstring in FlaskClient --- CHANGES | 2 ++ flask/testing.py | 14 ++++++++++++++ tests/test_testing.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/CHANGES b/CHANGES index 5a1f5a33..13ce156c 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Version 0.12 well as error handlers. - Disable logger propagation by default for the app logger. - Add support for range requests in ``send_file``. +- ``app.test_client`` includes preset default environment, which can now be + directly set, instead of per ``client.get``. Version 0.11.2 -------------- diff --git a/flask/testing.py b/flask/testing.py index 8eacf58b..31600245 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,6 +10,7 @@ :license: BSD, see LICENSE for more details. """ +import werkzeug from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack @@ -43,11 +44,23 @@ class FlaskClient(Client): information about how to use this class refer to :class:`werkzeug.test.Client`. + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + Basic usage is outlined in the :ref:`testing` chapter. """ preserve_context = False + def __init__(self, *args, **kwargs): + super(FlaskClient, self).__init__(*args, **kwargs) + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__ + } + @contextmanager def session_transaction(self, *args, **kwargs): """When used in combination with a ``with`` statement this opens a @@ -101,6 +114,7 @@ class FlaskClient(Client): def open(self, *args, **kwargs): kwargs.setdefault('environ_overrides', {}) \ ['flask._preserve_context'] = self.preserve_context + kwargs.setdefault('environ_base', self.environ_base) as_tuple = kwargs.pop('as_tuple', False) buffered = kwargs.pop('buffered', False) diff --git a/tests/test_testing.py b/tests/test_testing.py index 7bb99e79..9d353904 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -11,6 +11,7 @@ import pytest import flask +import werkzeug from flask._compat import text_type @@ -43,6 +44,40 @@ def test_environ_defaults(): rv = c.get('/') assert rv.data == b'http://localhost/' +def test_environ_base_default(): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + flask.g.user_agent = flask.request.headers["User-Agent"] + return flask.request.remote_addr + + with app.test_client() as c: + rv = c.get('/') + assert rv.data == b'127.0.0.1' + assert flask.g.user_agent == 'werkzeug/' + werkzeug.__version__ + +def test_environ_base_modified(): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + flask.g.user_agent = flask.request.headers["User-Agent"] + return flask.request.remote_addr + + with app.test_client() as c: + c.environ_base['REMOTE_ADDR'] = '0.0.0.0' + c.environ_base['HTTP_USER_AGENT'] = 'Foo' + rv = c.get('/') + assert rv.data == b'0.0.0.0' + assert flask.g.user_agent == 'Foo' + + c.environ_base['REMOTE_ADDR'] = '0.0.0.1' + c.environ_base['HTTP_USER_AGENT'] = 'Bar' + rv = c.get('/') + assert rv.data == b'0.0.0.1' + assert flask.g.user_agent == 'Bar' + def test_redirect_keep_session(): app = flask.Flask(__name__) app.secret_key = 'testing' From cdbd63d7de71250e0c84530c47f64bc518930ddb Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 12 Oct 2016 12:14:49 -0700 Subject: [PATCH 259/440] Windows venv is Scripts, capital S closes #2056 --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 91d95270..6f833eac 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -72,7 +72,7 @@ corresponding environment. On OS X and Linux, do the following:: If you are a Windows user, the following command is for you:: - $ venv\scripts\activate + $ venv\Scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). From d25c801a3b95551fd03eae82f916f92460cc2b34 Mon Sep 17 00:00:00 2001 From: Cody Date: Fri, 14 Oct 2016 04:13:42 -0400 Subject: [PATCH 260/440] add 'caution' section to docs, workaround for zero-padded file modes (#2057) Fix #2029 --- CONTRIBUTING.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ca7b4af2..d9cd2214 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -87,3 +87,28 @@ Generate a HTML report can be done using this command:: Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io +Caution +======= +pushing +------- +This repository contains several zero-padded file modes that may cause issues when pushing this repository to git hosts other than github. Fixing this is destructive to the commit history, so we suggest ignoring these warnings. If it fails to push and you're using a self-hosted git service like Gitlab, you can turn off repository checks in the admin panel. + + +cloning +------- +The zero-padded file modes files above can cause issues while cloning, too. If you have + +:: + + [fetch] + fsckobjects = true + +or + +:: + + [receive] + fsckObjects = true + + +set in your git configuration file, cloning this repository will fail. The only solution is to set both of the above settings to false while cloning, and then setting them back to true after the cloning is finished. From 2cc76628d288bf1d8817e0ae6b812551fc87faf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionu=C8=9B=20Cioc=C3=AErlan?= Date: Mon, 24 Oct 2016 14:20:54 +0300 Subject: [PATCH 261/440] Fix grammar in docs --- docs/becomingbig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst index df470a76..0facbfee 100644 --- a/docs/becomingbig.rst +++ b/docs/becomingbig.rst @@ -12,7 +12,7 @@ Flask started in part to demonstrate how to build your own framework on top of existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it developed, it became useful to a wide audience. As you grow your codebase, don't just use Flask -- understand it. Read the source. Flask's code is -written to be read; it's documentation is published so you can use its internal +written to be read; its documentation is published so you can use its internal APIs. Flask sticks to documented APIs in upstream libraries, and documents its internal utilities so that you can find the hook points needed for your project. From fa087c89298e0c91abff45b3ee7033889d2dd5d7 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Sun, 30 Oct 2016 09:34:49 -0400 Subject: [PATCH 262/440] Fixes import statement in flaskr (#2068) - `from flaskr.flaskr import app` in flaskr/__init__.py causes an import error with Python 2 - The relative import now used works for py2 and py3 --- docs/tutorial/packaging.rst | 2 +- examples/flaskr/flaskr/__init__.py | 2 +- examples/flaskr/setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/packaging.rst b/docs/tutorial/packaging.rst index 8db6531e..18f5c9b3 100644 --- a/docs/tutorial/packaging.rst +++ b/docs/tutorial/packaging.rst @@ -55,7 +55,7 @@ into this file, :file:`flaskr/__init__.py`: .. sourcecode:: python - from flaskr import app + from .flaskr import app This import statement brings the application instance into the top-level of the application package. When it is time to run the application, the diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py index 14a36539..3f782479 100644 --- a/examples/flaskr/flaskr/__init__.py +++ b/examples/flaskr/flaskr/__init__.py @@ -1 +1 @@ -from flaskr import app \ No newline at end of file +from .flaskr import app \ No newline at end of file diff --git a/examples/flaskr/setup.cfg b/examples/flaskr/setup.cfg index b7e47898..db50667a 100644 --- a/examples/flaskr/setup.cfg +++ b/examples/flaskr/setup.cfg @@ -1,2 +1,2 @@ -[aliases] +[tool:pytest] test=pytest From cb30a3b5628616c6d34c210eb429650f09c75334 Mon Sep 17 00:00:00 2001 From: Clenimar Filemon Date: Mon, 31 Oct 2016 13:41:38 -0300 Subject: [PATCH 263/440] Update docstring for errorhandler() (#2070) --- flask/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 59c77a15..68de2b90 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1153,7 +1153,8 @@ class Flask(_PackageBoundObject): that do not necessarily have to be a subclass of the :class:`~werkzeug.exceptions.HTTPException` class. - :param code: the code as integer for the handler + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception """ def decorator(f): self._register_error_handler(None, code_or_exception, f) From 6478d7bb99ef7dcdda19ce3e58c00500ff72e60b Mon Sep 17 00:00:00 2001 From: Alex Kahan Date: Mon, 31 Oct 2016 18:10:27 -0400 Subject: [PATCH 264/440] Adding coverage generation to tox (#2071) * Adding coverage generation to tox * Removing test directory from coverage command * Adding back to pytest command --- .gitignore | 6 ++++++ tox.ini | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9bf4f063..fb9baf35 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ _mailinglist .tox .cache/ .idea/ + +# Coverage reports +htmlcov +.coverage +.coverage.* +*,cover diff --git a/tox.ini b/tox.ini index 28942d3e..57725d0a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,11 +4,12 @@ envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35 [testenv] +usedevelop=true commands = - py.test [] - + py.test [] --cov=flask --cov-report html deps= pytest + pytest-cov greenlet lowest: Werkzeug==0.7 From de1652467b0584fe9ee4060bc42101e8255838a3 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Tue, 1 Nov 2016 14:35:17 +0000 Subject: [PATCH 265/440] Remove busy-work. (#2072) It is entirely sufficient to walk the MRO of the exception class, no need to check for classes re-appearing later on, no need to add the MRO of any superclass. * Python refuses point-blank to create a class with a circular MRO. * All classes in a superclass MRO *already* appear in the MRO of the derived type. Re-adding the contents of a superclass MRO is doing double work. --- flask/app.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/flask/app.py b/flask/app.py index 68de2b90..942992dc 100644 --- a/flask/app.py +++ b/flask/app.py @@ -14,7 +14,6 @@ from threading import Lock from datetime import timedelta from itertools import chain from functools import update_wrapper -from collections import deque from werkzeug.datastructures import ImmutableDict from werkzeug.routing import Map, Rule, RequestRedirect, BuildError @@ -1437,24 +1436,13 @@ class Flask(_PackageBoundObject): def find_handler(handler_map): if not handler_map: return - queue = deque(exc_class.__mro__) - # Protect from geniuses who might create circular references in - # __mro__ - done = set() - - while queue: - cls = queue.popleft() - if cls in done: - continue - done.add(cls) + for cls in exc_class.__mro__: handler = handler_map.get(cls) if handler is not None: # cache for next time exc_class is raised handler_map[exc_class] = handler return handler - queue.extend(cls.__mro__) - # try blueprint handlers handler = find_handler(self.error_handler_spec .get(request.blueprint, {}) From 11809bf1d2f8536a9a50942ddc2bd2b64b595199 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Nov 2016 13:11:53 -0700 Subject: [PATCH 266/440] Add license_file to setup.cfg metadata (#2024) Without this, the LICENSE file is never included in the built wheels: this makes it harder for users to comply with the license. With this addition a file LICENSE.txt will be created in the `xxx.dist-info` directory with the content of the `license_file` file, e.g. the top level LICENSE. --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 34414b3e..0e97b0ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,5 +4,8 @@ release = egg_info -RDb '' [wheel] universal = 1 +[metadata] +license_file = LICENSE + [tool:pytest] norecursedirs = .* *.egg *.egg-info env* artwork docs examples From 77af942b982a3b07669362cc6bc223026bf039cd Mon Sep 17 00:00:00 2001 From: Clenimar Filemon Date: Tue, 1 Nov 2016 22:52:32 -0300 Subject: [PATCH 267/440] Capitalize occurrences of 'flask' (#2067) --- flask/__init__.py | 2 +- flask/cli.py | 4 ++-- flask/sessions.py | 2 +- flask/signals.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index 509b944f..b1360e84 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -40,7 +40,7 @@ from .signals import signals_available, template_rendered, request_started, \ # it. from . import json -# This was the only thing that flask used to export at one point and it had +# This was the only thing that Flask used to export at one point and it had # a more generic name. jsonify = json.jsonify diff --git a/flask/cli.py b/flask/cli.py index 6c8cf32d..da988e80 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -131,9 +131,9 @@ version_option = click.Option(['--version'], is_flag=True, is_eager=True) class DispatchingApp(object): - """Special application that dispatches to a flask application which + """Special application that dispatches to a Flask application which is imported by name in a background thread. If an error happens - it is is recorded and shows as part of the WSGI handling which in case + it is recorded and shown as part of the WSGI handling which in case of the Werkzeug debugger means that it shows up in the browser. """ diff --git a/flask/sessions.py b/flask/sessions.py index b9120712..4d67658a 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -168,7 +168,7 @@ class SessionInterface(object): null_session_class = NullSession #: A flag that indicates if the session interface is pickle based. - #: This can be used by flask extensions to make a decision in regards + #: This can be used by Flask extensions to make a decision in regards #: to how to deal with the session object. #: #: .. versionadded:: 0.10 diff --git a/flask/signals.py b/flask/signals.py index c9b8a210..dd52cdb5 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -37,7 +37,7 @@ except ImportError: temporarily_connected_to = connected_to = _fail del _fail -# The namespace for code signals. If you are not flask code, do +# The namespace for code signals. If you are not Flask code, do # not put signals in here. Create your own namespace instead. _signals = Namespace() From 9685d14eaa78de991e80db41e839fe5e932ee95f Mon Sep 17 00:00:00 2001 From: Shandy Brown Date: Tue, 1 Nov 2016 18:52:54 -0700 Subject: [PATCH 268/440] Correct grammar (#2061) --- docs/patterns/sqlalchemy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 40e048e0..e8215317 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -135,7 +135,7 @@ Here is an example :file:`database.py` module for your application:: def init_db(): metadata.create_all(bind=engine) -As for the declarative approach you need to close the session after +As in the declarative approach, you need to close the session after each request or application context shutdown. Put this into your application module:: From bbe58a4944d67087b5cf59b54aa31a8834055187 Mon Sep 17 00:00:00 2001 From: Tery Lim Date: Wed, 2 Nov 2016 15:58:22 +1300 Subject: [PATCH 269/440] Update errorhandling.rst (#2074) --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 3bda5f15..18493c67 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -216,7 +216,7 @@ A formatter can be instantiated with a format string. Note that tracebacks are appended to the log entry automatically. You don't have to do that in the log formatter format string. -Here some example setups: +Here are some example setups: Email ````` From ec9717502f36126b8a371b73a5730b2092948ca2 Mon Sep 17 00:00:00 2001 From: Tery Lim Date: Wed, 2 Nov 2016 17:04:20 +1300 Subject: [PATCH 270/440] Update errorhandling.rst (#2075) Fix LogRecord class reference. --- docs/errorhandling.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 18493c67..64c0f8b3 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -276,8 +276,9 @@ that this list is not complete, consult the official documentation of the | ``%(lineno)d`` | Source line number where the logging call was | | | issued (if available). | +------------------+----------------------------------------------------+ -| ``%(asctime)s`` | Human-readable time when the LogRecord` was | -| | created. By default this is of the form | +| ``%(asctime)s`` | Human-readable time when the | +| | :class:`~logging.LogRecord` was created. | +| | By default this is of the form | | | ``"2003-07-08 16:49:45,896"`` (the numbers after | | | the comma are millisecond portion of the time). | | | This can be changed by subclassing the formatter | From a4ed3d28066bb1625c15fc6a89c1533535dc7879 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 2 Nov 2016 17:56:59 +0100 Subject: [PATCH 271/440] Use tox from make test --- Makefile | 7 ++----- setup.cfg | 2 +- test-requirements.txt | 2 +- tox.ini | 6 +++++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 9bcdebc2..f76c2099 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,8 @@ all: clean-pyc test test: - pip install -r test-requirements.txt -q - FLASK_DEBUG= py.test tests examples - -tox-test: - tox + pip install -r test-requirements.txt + tox -e py-release audit: python setup.py audit diff --git a/setup.cfg b/setup.cfg index 0e97b0ba..cd282e48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ universal = 1 license_file = LICENSE [tool:pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs examples +norecursedirs = .* *.egg *.egg-info env* artwork docs diff --git a/test-requirements.txt b/test-requirements.txt index e079f8a6..053148f8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1 @@ -pytest +tox diff --git a/tox.ini b/tox.ini index 57725d0a..406de5dd 100644 --- a/tox.ini +++ b/tox.ini @@ -4,9 +4,13 @@ envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35 [testenv] +passenv = LANG usedevelop=true commands = - py.test [] --cov=flask --cov-report html + # We need to install those after Flask is installed. + pip install -e examples/flaskr + pip install -e examples/minitwit + py.test --cov=flask --cov-report html [] deps= pytest pytest-cov From 2647fc7112ed79c1751a3d99491d7dcfe0aa4520 Mon Sep 17 00:00:00 2001 From: Alex Kahan Date: Thu, 3 Nov 2016 13:11:24 -0400 Subject: [PATCH 272/440] Parameterizing test (#2073) --- tests/test_helpers.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8348331b..69fbaf3b 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -113,20 +113,17 @@ class TestJSON(object): rv = flask.json.load(out) assert rv == test_data - def test_jsonify_basic_types(self): + @pytest.mark.parametrize('test_value', [0, -1, 1, 23, 3.14, 's', "longer string", True, False, None]) + def test_jsonify_basic_types(self, test_value): """Test jsonify with basic types.""" - # Should be able to use pytest parametrize on this, but I couldn't - # figure out the correct syntax - # https://pytest.org/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions - test_data = (0, 1, 23, 3.14, 's', "longer string", True, False,) app = flask.Flask(__name__) c = app.test_client() - for i, d in enumerate(test_data): - url = '/jsonify_basic_types{0}'.format(i) - app.add_url_rule(url, str(i), lambda x=d: flask.jsonify(x)) - rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data) == d + + url = '/jsonify_basic_types' + app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x)) + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == test_value def test_jsonify_dicts(self): """Test jsonify with dicts and kwargs unpacking.""" @@ -170,12 +167,10 @@ class TestJSON(object): def test_jsonify_date_types(self): """Test jsonify with datetime.date and datetime.datetime types.""" - test_dates = ( datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5) ) - app = flask.Flask(__name__) c = app.test_client() @@ -189,8 +184,7 @@ class TestJSON(object): def test_jsonify_uuid_types(self): """Test jsonify with uuid.UUID types""" - test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4) - + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF' * 4) app = flask.Flask(__name__) url = '/uuid_test' app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) From 281c9c3ff9f4a49960c07b23cad4554a5c349272 Mon Sep 17 00:00:00 2001 From: Giles Thomas Date: Mon, 7 Nov 2016 18:10:02 +0000 Subject: [PATCH 273/440] Added a link to instructions for PythonAnywhere (#2081) --- docs/deploying/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 5d88cf72..95e96bf2 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -23,6 +23,7 @@ Hosted options - `Deploying Flask on Google App Engine `_ - `Sharing your Localhost Server with Localtunnel `_ - `Deploying on Azure (IIS) `_ +- `Deploying on PythonAnywhere `_ Self-hosted options ------------------- From 4cf4229355459ecb47b0f862302848ce92a5bbbc Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 15 Nov 2016 11:57:09 +0100 Subject: [PATCH 274/440] Fix rST rendering of env var (#2085) This was broken in https://github.com/pallets/flask/commit/ad011bc32d7b9160354efafcd43e20f7042a6a13#diff-fd40cf2be7711772de9d8316da038cceR263 --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 89fa0924..6d37c1e8 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -262,7 +262,7 @@ So a common pattern is this:: This first loads the configuration from the `yourapplication.default_settings` module and then overrides the values -with the contents of the file the :envvar:``YOURAPPLICATION_SETTINGS`` +with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS` environment variable points to. This environment variable can be set on Linux or OS X with the export command in the shell before starting the server:: From 7e1a13ffbd212e928e866c2e4cf5d92d85660f86 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 15 Nov 2016 11:56:51 +0100 Subject: [PATCH 275/440] Fix import error --- examples/minitwit/minitwit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minitwit/minitwit/__init__.py b/examples/minitwit/minitwit/__init__.py index 0b8bd697..96c81aec 100644 --- a/examples/minitwit/minitwit/__init__.py +++ b/examples/minitwit/minitwit/__init__.py @@ -1 +1 @@ -from minitwit import app \ No newline at end of file +from .minitwit import app From 4a8bf651d9177fbe745fddd1aa2ab098b0670437 Mon Sep 17 00:00:00 2001 From: ezramorris Date: Thu, 17 Nov 2016 14:01:30 +0000 Subject: [PATCH 276/440] Add link to AWS EB Flask tutorial --- docs/deploying/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 95e96bf2..20e71762 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -21,6 +21,7 @@ Hosted options - `Deploying Flask on OpenShift `_ - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ +- `Deploying Flask on AWS Elastic Beanstalk `_ - `Sharing your Localhost Server with Localtunnel `_ - `Deploying on Azure (IIS) `_ - `Deploying on PythonAnywhere `_ From ccb562854efad180bcbd37f126757d493ed68ff3 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Mon, 19 Dec 2016 14:37:34 +0100 Subject: [PATCH 277/440] Remove wrong comma (#2116) --- docs/patterns/appfactories.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index dc9660ae..c118a273 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -6,7 +6,7 @@ Application Factories If you are already using packages and blueprints for your application (:ref:`blueprints`) there are a couple of really nice ways to further improve the experience. A common pattern is creating the application object when -the blueprint is imported. But if you move the creation of this object, +the blueprint is imported. But if you move the creation of this object into a function, you can then create multiple instances of this app later. So why would you want to do this? From 0ba1a872b7aac48efef9028e7687475da34eaa39 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Wed, 21 Dec 2016 21:06:48 +0100 Subject: [PATCH 278/440] Style the flask command consistently (#2120) It's done like this in other parts of this doc. --- docs/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 7ddf50f3..2ca0e83e 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -139,8 +139,8 @@ This could be a file named :file:`autoapp.py` with these contents:: from yourapplication import create_app app = create_app(os.environ['YOURAPPLICATION_CONFIG']) -Once this has happened you can make the flask command automatically pick -it up:: +Once this has happened you can make the :command:`flask` command automatically +pick it up:: export YOURAPPLICATION_CONFIG=/path/to/config.cfg export FLASK_APP=/path/to/autoapp.py From 7f288371674b420ac2df8ff2313e3b469662ee41 Mon Sep 17 00:00:00 2001 From: Hopsken Date: Thu, 22 Dec 2016 04:07:09 +0800 Subject: [PATCH 279/440] Update README for minitwit (#2119) add step 2 to run minitwit --- examples/minitwit/README | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/minitwit/README b/examples/minitwit/README index 4561d836..b9bc5ea2 100644 --- a/examples/minitwit/README +++ b/examples/minitwit/README @@ -14,15 +14,19 @@ export an MINITWIT_SETTINGS environment variable pointing to a configuration file. - 2. tell flask about the right application: + 2. install the app from the root of the project directory + + pip install --editable . + + 3. tell flask about the right application: export FLASK_APP=minitwit - 2. fire up a shell and run this: + 4. fire up a shell and run this: flask initdb - 3. now you can run minitwit: + 5. now you can run minitwit: flask run From 0e79aba40d2497218736448ced708fcf4f8943b3 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 21 Dec 2016 12:07:57 -0800 Subject: [PATCH 280/440] use dict instead of if/else logic (#2093) --- flask/sessions.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 4d67658a..525ff246 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -84,21 +84,25 @@ class TaggedJSONSerializer(object): def dumps(self, value): return json.dumps(_tag(value), separators=(',', ':')) + LOADS_MAP = { + ' t': tuple, + ' u': uuid.UUID, + ' b': b64decode, + ' m': Markup, + ' d': parse_date, + } + def loads(self, value): def object_hook(obj): if len(obj) != 1: return obj the_key, the_value = next(iteritems(obj)) - if the_key == ' t': - return tuple(the_value) - elif the_key == ' u': - return uuid.UUID(the_value) - elif the_key == ' b': - return b64decode(the_value) - elif the_key == ' m': - return Markup(the_value) - elif the_key == ' d': - return parse_date(the_value) + # Check the key for a corresponding function + return_function = self.LOADS_MAP.get(the_key) + if return_function: + # Pass the value to the function + return return_function(the_value) + # Didn't find a function for this object return obj return json.loads(value, object_hook=object_hook) From 36425d5f91b57210f7707de9564377fea93825b2 Mon Sep 17 00:00:00 2001 From: Jiri Kuncar Date: Wed, 21 Dec 2016 21:08:38 +0100 Subject: [PATCH 281/440] Ignore cache on request.get_json(cache=False) call (#2089) * Test cache argument of Request.get_json * Ignore cache on request.get_json(cache=False) call Removes usage of `_cached_json` property when `get_json` is called with disabled cache argument. (closes #2087) --- flask/wrappers.py | 3 ++- tests/test_helpers.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index d1d7ba7d..04bdcb5d 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -137,7 +137,8 @@ class Request(RequestBase): on the request. """ rv = getattr(self, '_cached_json', _missing) - if rv is not _missing: + # We return cached JSON only when the cache is enabled. + if cache and rv is not _missing: return rv if not (force or self.is_json): diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 69fbaf3b..3e2ea8cd 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -35,6 +35,14 @@ def has_encoding(name): class TestJSON(object): + def test_ignore_cached_json(self): + app = flask.Flask(__name__) + with app.test_request_context('/', method='POST', data='malformed', + content_type='application/json'): + assert flask.request.get_json(silent=True, cache=True) is None + with pytest.raises(BadRequest): + flask.request.get_json(silent=False, cache=False) + def test_post_empty_json_adds_exception_to_response_content_in_debug(self): app = flask.Flask(__name__) app.config['DEBUG'] = True From 45c45ea73c993cd12334194d62e0f83027a46b07 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 21 Dec 2016 21:19:53 +0100 Subject: [PATCH 282/440] Version 0.12 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 13ce156c..73e78128 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,8 @@ Here you can see the full list of changes between each Flask release. Version 0.12 ------------ +Released on December 21st 2016, codename Punsch. + - the cli command now responds to `--version`. - Mimetype guessing and ETag generation for file-like objects in ``send_file`` has been removed, as per issue ``#104``. See pull request ``#1849``. From 1042d9d23f3c61f4474aea568a359337cf450fab Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 21 Dec 2016 21:22:08 +0100 Subject: [PATCH 283/440] Bump version number to 0.12 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index b1360e84..69f29fb4 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.11.2-dev' +__version__ = '0.12' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From b2e0886f484ac4544afd440955aa936379161e7c Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 21 Dec 2016 21:22:26 +0100 Subject: [PATCH 284/440] Bump to dev version --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 69f29fb4..bb6c4c18 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12' +__version__ = '0.13-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From caf6b8c3141bc1c251952600a80690fcb5e11009 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 25 Dec 2016 16:33:55 +0100 Subject: [PATCH 285/440] Changelog stub for 0.12.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 73e78128..317bd489 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.12.1 +-------------- + +Bugfix release, unreleased + Version 0.12 ------------ From 8cd0b03beeac4a41c398ea365475c651c484a9ee Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 25 Dec 2016 16:34:22 +0100 Subject: [PATCH 286/440] Bump to dev 0.12.1 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 69f29fb4..3cef3b43 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12' +__version__ = '0.12.1-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 789715adb9949f58b7b0272bed1a58d7cd0fad30 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 26 Dec 2016 03:50:47 +0100 Subject: [PATCH 287/440] Fix config.from_pyfile on Python 3 (#2123) * Fix config.from_pyfile on Python 3 Fix #2118 * Support Python 2.6 * Fix tests on Python 2 --- CHANGES | 3 +++ flask/config.py | 2 +- tests/test_config.py | 22 ++++++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 317bd489..a0a423d1 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Version 0.12.1 Bugfix release, unreleased +- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix + ``#2118``. + Version 0.12 ------------ diff --git a/flask/config.py b/flask/config.py index 36e8a123..697add71 100644 --- a/flask/config.py +++ b/flask/config.py @@ -126,7 +126,7 @@ class Config(dict): d = types.ModuleType('config') d.__file__ = filename try: - with open(filename) as config_file: + 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): diff --git a/tests/test_config.py b/tests/test_config.py index 333a5cff..5c98db98 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,11 +7,14 @@ :license: BSD, see LICENSE for more details. """ -import pytest -import os from datetime import timedelta +import os +import textwrap + import flask +from flask._compat import PY2 +import pytest # config keys used for the TestConfig @@ -187,3 +190,18 @@ def test_get_namespace(): assert 2 == len(bar_options) assert 'bar stuff 1' == bar_options['BAR_STUFF_1'] assert 'bar stuff 2' == bar_options['BAR_STUFF_2'] + + +@pytest.mark.parametrize('encoding', ['utf-8', 'iso-8859-15', 'latin-1']) +def test_from_pyfile_weird_encoding(tmpdir, encoding): + f = tmpdir.join('my_config.py') + f.write_binary(textwrap.dedent(u''' + # -*- coding: {0} -*- + TEST_VALUE = "föö" + '''.format(encoding)).encode(encoding)) + app = flask.Flask(__name__) + app.config.from_pyfile(str(f)) + value = app.config['TEST_VALUE'] + if PY2: + value = value.decode(encoding) + assert value == u'föö' From 079d752ceca6ea28d953198d643c2cdd1f895531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Wed, 28 Dec 2016 10:11:33 -0500 Subject: [PATCH 288/440] Update Flask-SQLAlchemy link (#2126) --- docs/patterns/sqlalchemy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index e8215317..9c985cc6 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -22,7 +22,7 @@ if you want to get started quickly. You can download `Flask-SQLAlchemy`_ from `PyPI `_. -.. _Flask-SQLAlchemy: http://pythonhosted.org/Flask-SQLAlchemy/ +.. _Flask-SQLAlchemy: http://flask-sqlalchemy.pocoo.org/ Declarative From e49b73d2cf7d08a6f3aa3940b4492ee1f3176787 Mon Sep 17 00:00:00 2001 From: wgwz Date: Fri, 30 Dec 2016 12:43:31 -0500 Subject: [PATCH 289/440] Adds the largerapp from the docs as an example --- examples/largerapp/setup.py | 10 ++++++++++ examples/largerapp/yourapplication/__init__.py | 4 ++++ examples/largerapp/yourapplication/static/style.css | 0 .../largerapp/yourapplication/templates/index.html | 0 .../largerapp/yourapplication/templates/layout.html | 0 .../largerapp/yourapplication/templates/login.html | 0 examples/largerapp/yourapplication/views.py | 5 +++++ 7 files changed, 19 insertions(+) create mode 100644 examples/largerapp/setup.py create mode 100644 examples/largerapp/yourapplication/__init__.py create mode 100644 examples/largerapp/yourapplication/static/style.css create mode 100644 examples/largerapp/yourapplication/templates/index.html create mode 100644 examples/largerapp/yourapplication/templates/layout.html create mode 100644 examples/largerapp/yourapplication/templates/login.html create mode 100644 examples/largerapp/yourapplication/views.py diff --git a/examples/largerapp/setup.py b/examples/largerapp/setup.py new file mode 100644 index 00000000..eaf00f07 --- /dev/null +++ b/examples/largerapp/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup + +setup( + name='yourapplication', + packages=['yourapplication'], + include_package_data=True, + install_requires=[ + 'flask', + ], +) diff --git a/examples/largerapp/yourapplication/__init__.py b/examples/largerapp/yourapplication/__init__.py new file mode 100644 index 00000000..089d2937 --- /dev/null +++ b/examples/largerapp/yourapplication/__init__.py @@ -0,0 +1,4 @@ +from flask import Flask +app = Flask(__name__) + +import yourapplication.views \ No newline at end of file diff --git a/examples/largerapp/yourapplication/static/style.css b/examples/largerapp/yourapplication/static/style.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/templates/index.html b/examples/largerapp/yourapplication/templates/index.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/templates/layout.html b/examples/largerapp/yourapplication/templates/layout.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/templates/login.html b/examples/largerapp/yourapplication/templates/login.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/views.py b/examples/largerapp/yourapplication/views.py new file mode 100644 index 00000000..b112328e --- /dev/null +++ b/examples/largerapp/yourapplication/views.py @@ -0,0 +1,5 @@ +from yourapplication import app + +@app.route('/') +def index(): + return 'Hello World!' \ No newline at end of file From 949771adf51ecaea685b57cb37089b4772f9d285 Mon Sep 17 00:00:00 2001 From: wgwz Date: Fri, 30 Dec 2016 12:50:13 -0500 Subject: [PATCH 290/440] Add reference to largerapp src in docs --- docs/patterns/packages.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 1cd77974..d1780ca8 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -114,6 +114,10 @@ You should then end up with something like that:: login.html ... +If you find yourself stuck on something, feel free +to take a look at the source code for this example. +You'll find it located under ``flask/examples/largerapp``. + .. admonition:: Circular Imports Every Python programmer hates them, and yet we just added some: From 582a878ad96b59207931635fa65704c5ab295535 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 30 Dec 2016 22:28:43 +0100 Subject: [PATCH 291/440] Init 0.13 changelog --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index a0a423d1..91d4813b 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.13 +------------ + +Major release, unreleased + Version 0.12.1 -------------- From 0832e77b145b226bac4ee82144221800a0f7d34a Mon Sep 17 00:00:00 2001 From: Paul Brown Date: Fri, 30 Dec 2016 15:02:08 -0600 Subject: [PATCH 292/440] prevent NoAppException when ImportError occurs within imported module --- flask/cli.py | 14 ++++++++++---- tests/test_apps/cliapp/importerrorapp.py | 7 +++++++ tests/test_cli.py | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 tests/test_apps/cliapp/importerrorapp.py diff --git a/flask/cli.py b/flask/cli.py index da988e80..074ee768 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -89,10 +89,16 @@ def locate_app(app_id): try: __import__(module) except ImportError: - raise NoAppException('The file/path provided (%s) does not appear to ' - 'exist. Please verify the path is correct. If ' - 'app is not on PYTHONPATH, ensure the extension ' - 'is .py' % module) + # Reraise the ImportError if it occurred within the imported module. + # Determine this by checking whether the trace has a depth > 1. + if sys.exc_info()[-1].tb_next: + raise + else: + raise NoAppException('The file/path provided (%s) does not appear' + ' to exist. Please verify the path is ' + 'correct. If app is not on PYTHONPATH, ' + 'ensure the extension is .py' % module) + mod = sys.modules[module] if app_obj is None: app = find_best_app(mod) diff --git a/tests/test_apps/cliapp/importerrorapp.py b/tests/test_apps/cliapp/importerrorapp.py new file mode 100644 index 00000000..fb87c9b1 --- /dev/null +++ b/tests/test_apps/cliapp/importerrorapp.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, print_function + +from flask import Flask + +raise ImportError() + +testapp = Flask('testapp') diff --git a/tests/test_cli.py b/tests/test_cli.py index 18026a75..313a34d2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -83,6 +83,7 @@ def test_locate_app(test_apps): pytest.raises(NoAppException, locate_app, "notanpp.py") pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") + pytest.raises(ImportError, locate_app, "cliapp.importerrorapp") def test_find_default_import_path(test_apps, monkeypatch, tmpdir): From 31e25facd3972b42d118792c847299cc8b2b25a5 Mon Sep 17 00:00:00 2001 From: Paul Brown Date: Fri, 30 Dec 2016 15:40:30 -0600 Subject: [PATCH 293/440] update change log --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index a0a423d1..03194421 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Version 0.12.1 Bugfix release, unreleased +- Prevent `flask run` from showing a NoAppException when an ImportError occurs + within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. From 49386ee69e92aa23edefbfff01fd425c5fbe33d1 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 31 Dec 2016 16:31:44 +0100 Subject: [PATCH 294/440] Inherit Werkzeug docs (#2135) Fix #2132 --- docs/api.rst | 58 +--------------------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d77da3de..7da28dc8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -30,61 +30,12 @@ Incoming Request Data .. autoclass:: Request :members: - - .. attribute:: form - - A :class:`~werkzeug.datastructures.MultiDict` with the parsed form data from ``POST`` - or ``PUT`` requests. Please keep in mind that file uploads will not - end up here, but instead in the :attr:`files` attribute. - - .. attribute:: args - - A :class:`~werkzeug.datastructures.MultiDict` with the parsed contents of the query - string. (The part in the URL after the question mark). - - .. attribute:: values - - A :class:`~werkzeug.datastructures.CombinedMultiDict` with the contents of both - :attr:`form` and :attr:`args`. - - .. attribute:: cookies - - A :class:`dict` with the contents of all cookies transmitted with - the request. - - .. attribute:: stream - - If the incoming form data was not encoded with a known mimetype - the data is stored unmodified in this stream for consumption. Most - of the time it is a better idea to use :attr:`data` which will give - you that data as a string. The stream only returns the data once. - - .. attribute:: headers - - The incoming request headers as a dictionary like object. - - .. attribute:: data - - Contains the incoming request data as string in case it came with - a mimetype Flask does not handle. - - .. attribute:: files - - A :class:`~werkzeug.datastructures.MultiDict` with files uploaded as part of a - ``POST`` or ``PUT`` request. Each file is stored as - :class:`~werkzeug.datastructures.FileStorage` object. It basically behaves like a - standard file object you know from Python, with the difference that - it also has a :meth:`~werkzeug.datastructures.FileStorage.save` function that can - store the file on the filesystem. + :inherited-members: .. attribute:: environ The underlying WSGI environment. - .. attribute:: method - - The current request method (``POST``, ``GET`` etc.) - .. attribute:: path .. attribute:: full_path .. attribute:: script_root @@ -114,13 +65,6 @@ Incoming Request Data `url_root` ``u'http://www.example.com/myapplication/'`` ============= ====================================================== - .. attribute:: is_xhr - - ``True`` if the request was triggered via a JavaScript - `XMLHttpRequest`. This only works with libraries that support the - ``X-Requested-With`` header and set it to `XMLHttpRequest`. - Libraries that do that are prototype, jQuery and Mochikit and - probably some more. .. class:: request From 92fa444259fe32debf922eddbd6d18e7d69fdfaa Mon Sep 17 00:00:00 2001 From: wgwz Date: Sat, 31 Dec 2016 12:08:25 -0500 Subject: [PATCH 295/440] Moves largerapp into patterns dir and add test - also adds this pattern into tox for testing --- examples/{ => patterns}/largerapp/setup.py | 0 examples/patterns/largerapp/tests/test_largerapp.py | 12 ++++++++++++ .../largerapp/yourapplication/__init__.py | 0 .../largerapp/yourapplication/static/style.css | 0 .../largerapp/yourapplication/templates/index.html | 0 .../largerapp/yourapplication/templates/layout.html | 0 .../largerapp/yourapplication/templates/login.html | 0 .../largerapp/yourapplication/views.py | 0 tox.ini | 1 + 9 files changed, 13 insertions(+) rename examples/{ => patterns}/largerapp/setup.py (100%) create mode 100644 examples/patterns/largerapp/tests/test_largerapp.py rename examples/{ => patterns}/largerapp/yourapplication/__init__.py (100%) rename examples/{ => patterns}/largerapp/yourapplication/static/style.css (100%) rename examples/{ => patterns}/largerapp/yourapplication/templates/index.html (100%) rename examples/{ => patterns}/largerapp/yourapplication/templates/layout.html (100%) rename examples/{ => patterns}/largerapp/yourapplication/templates/login.html (100%) rename examples/{ => patterns}/largerapp/yourapplication/views.py (100%) diff --git a/examples/largerapp/setup.py b/examples/patterns/largerapp/setup.py similarity index 100% rename from examples/largerapp/setup.py rename to examples/patterns/largerapp/setup.py diff --git a/examples/patterns/largerapp/tests/test_largerapp.py b/examples/patterns/largerapp/tests/test_largerapp.py new file mode 100644 index 00000000..acd33c5c --- /dev/null +++ b/examples/patterns/largerapp/tests/test_largerapp.py @@ -0,0 +1,12 @@ +from yourapplication import app +import pytest + +@pytest.fixture +def client(request): + app.config['TESTING'] = True + client = app.test_client() + return client + +def test_index(client): + rv = client.get('/') + assert b"Hello World!" in rv.data \ No newline at end of file diff --git a/examples/largerapp/yourapplication/__init__.py b/examples/patterns/largerapp/yourapplication/__init__.py similarity index 100% rename from examples/largerapp/yourapplication/__init__.py rename to examples/patterns/largerapp/yourapplication/__init__.py diff --git a/examples/largerapp/yourapplication/static/style.css b/examples/patterns/largerapp/yourapplication/static/style.css similarity index 100% rename from examples/largerapp/yourapplication/static/style.css rename to examples/patterns/largerapp/yourapplication/static/style.css diff --git a/examples/largerapp/yourapplication/templates/index.html b/examples/patterns/largerapp/yourapplication/templates/index.html similarity index 100% rename from examples/largerapp/yourapplication/templates/index.html rename to examples/patterns/largerapp/yourapplication/templates/index.html diff --git a/examples/largerapp/yourapplication/templates/layout.html b/examples/patterns/largerapp/yourapplication/templates/layout.html similarity index 100% rename from examples/largerapp/yourapplication/templates/layout.html rename to examples/patterns/largerapp/yourapplication/templates/layout.html diff --git a/examples/largerapp/yourapplication/templates/login.html b/examples/patterns/largerapp/yourapplication/templates/login.html similarity index 100% rename from examples/largerapp/yourapplication/templates/login.html rename to examples/patterns/largerapp/yourapplication/templates/login.html diff --git a/examples/largerapp/yourapplication/views.py b/examples/patterns/largerapp/yourapplication/views.py similarity index 100% rename from examples/largerapp/yourapplication/views.py rename to examples/patterns/largerapp/yourapplication/views.py diff --git a/tox.ini b/tox.ini index 406de5dd..c070a629 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ commands = # We need to install those after Flask is installed. pip install -e examples/flaskr pip install -e examples/minitwit + pip install -e examples/patterns/largerapp py.test --cov=flask --cov-report html [] deps= pytest From 46c1383919454ae281967316d6d6fb33bce9b773 Mon Sep 17 00:00:00 2001 From: wgwz Date: Sat, 31 Dec 2016 12:37:39 -0500 Subject: [PATCH 296/440] Remove unneccessary arg in client fixture --- examples/patterns/largerapp/tests/test_largerapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/patterns/largerapp/tests/test_largerapp.py b/examples/patterns/largerapp/tests/test_largerapp.py index acd33c5c..6bc0531e 100644 --- a/examples/patterns/largerapp/tests/test_largerapp.py +++ b/examples/patterns/largerapp/tests/test_largerapp.py @@ -2,7 +2,7 @@ from yourapplication import app import pytest @pytest.fixture -def client(request): +def client(): app.config['TESTING'] = True client = app.test_client() return client From 1b7258f816c2e025acc03a4e775d41a9d4477850 Mon Sep 17 00:00:00 2001 From: wgwz Date: Sat, 31 Dec 2016 18:51:00 -0500 Subject: [PATCH 297/440] Provides a link to the examples src - moved the link towards the top for better visibility --- docs/patterns/packages.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index d1780ca8..1bb84f8c 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -17,6 +17,10 @@ this:: login.html ... +If you find yourself stuck on something, feel free +to take a look at the source code for this example. +You'll find `the full src for this example here`_. + Simple Packages --------------- @@ -114,10 +118,6 @@ You should then end up with something like that:: login.html ... -If you find yourself stuck on something, feel free -to take a look at the source code for this example. -You'll find it located under ``flask/examples/largerapp``. - .. admonition:: Circular Imports Every Python programmer hates them, and yet we just added some: @@ -134,6 +134,7 @@ You'll find it located under ``flask/examples/largerapp``. .. _working-with-modules: +.. _the full src for this example here: https://github.com/pallets/flask/tree/master/examples/patterns/largerapp Working with Blueprints ----------------------- From 09973a7387d6239bae310af6c1d580f191b47c0a Mon Sep 17 00:00:00 2001 From: Bryce Guinta Date: Sun, 1 Jan 2017 19:51:21 -0700 Subject: [PATCH 298/440] Fix fastcgi lighttpd example documentation. (#2138) Add a trailing slash to the dummy path in the fastcgi lighttpd setup documentation. Omitting a trailing slash leads to unintended behavior. --- docs/deploying/fastcgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index c0beae0c..efae5163 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -144,7 +144,7 @@ A basic FastCGI configuration for lighttpd looks like that:: ) alias.url = ( - "/static/" => "/path/to/your/static" + "/static/" => "/path/to/your/static/" ) url.rewrite-once = ( From 88111ae6bf2e33f7e43c44e0cbb32b3d952e4a3a Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 10 Jan 2017 13:12:18 +0100 Subject: [PATCH 299/440] Do not suggest deprecated flask.ext.* --- docs/extensiondev.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index d73d6019..c9a72094 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -29,12 +29,6 @@ be something like "Flask-SimpleXML". Make sure to include the name This is how users can then register dependencies to your extension in their :file:`setup.py` files. -Flask sets up a redirect package called :data:`flask.ext` where users -should import the extensions from. If you for instance have a package -called ``flask_something`` users would import it as -``flask.ext.something``. This is done to transition from the old -namespace packages. See :ref:`ext-import-transition` for more details. - But what do extensions look like themselves? An extension has to ensure that it works with multiple Flask application instances at once. This is a requirement because many people will use patterns like the @@ -393,8 +387,6 @@ extension to be approved you have to follow these guidelines: Python 2.7 -.. _ext-import-transition: - Extension Import Transition --------------------------- From 01b992b1a1482246d705ffe3b3d0dd7816f0456b Mon Sep 17 00:00:00 2001 From: Andrew Arendt Date: Tue, 10 Jan 2017 11:20:53 -0600 Subject: [PATCH 300/440] Added python3.6 support for tests --- .travis.yml | 6 +++++- tests/test_basic.py | 2 +- tests/test_ext.py | 4 ++-- tox.ini | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f99a7e8..32247c58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: - "3.3" - "3.4" - "3.5" + - "3.6" env: - REQUIREMENTS=lowest @@ -32,7 +33,10 @@ matrix: env: REQUIREMENTS=lowest - python: "3.5" env: REQUIREMENTS=lowest-simplejson - + - python: "3.6" + env: REQUIREMENTS=lowest + - python: "3.6" + env: REQUIREMENTS=lowest-simplejson install: - pip install tox diff --git a/tests/test_basic.py b/tests/test_basic.py index be3d5edd..a099c904 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -333,7 +333,7 @@ def test_session_expiration(): client = app.test_client() rv = client.get('/') assert 'set-cookie' in rv.headers - match = re.search(r'\bexpires=([^;]+)(?i)', rv.headers['set-cookie']) + match = re.search(r'(?i)\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime assert expires.year == expected.year diff --git a/tests/test_ext.py b/tests/test_ext.py index d336e404..ebb5f02d 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -179,8 +179,8 @@ def test_flaskext_broken_package_no_module_caching(flaskext_broken): def test_no_error_swallowing(flaskext_broken): with pytest.raises(ImportError) as excinfo: import flask.ext.broken - - assert excinfo.type is ImportError + # python3.6 raises a subclass of ImportError: 'ModuleNotFoundError' + assert issubclass(excinfo.type, ImportError) if PY2: message = 'No module named missing_module' else: diff --git a/tox.ini b/tox.ini index 406de5dd..1ffdd5da 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35}-{release,devel}{,-simplejson} +envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35,py36}-{release,devel}{,-simplejson} From 9dcfd05d295574488bd0c3644599d1e97dcca3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=BD?= Date: Fri, 13 Jan 2017 10:54:55 +0100 Subject: [PATCH 301/440] Use SOURCE_DATE_EPOCH for copyright year to make build reproducible Details: https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal --- docs/conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b37427a8..81106a3a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,10 +11,13 @@ # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function -from datetime import datetime import os import sys import pkg_resources +import time +import datetime + +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, # add these directories to sys.path here. If the directory is relative to the @@ -49,7 +52,7 @@ master_doc = 'index' # General information about the project. project = u'Flask' -copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().year) +copyright = u'2010 - {0}, Armin Ronacher'.format(BUILD_DATE.year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 9900a72fe7992d873915af68f7d52148e738d032 Mon Sep 17 00:00:00 2001 From: Dennis Chen Date: Sat, 14 Jan 2017 12:58:45 -0800 Subject: [PATCH 302/440] Fix Request Reference (#2151) Points flask.Request to appropriate place in the documentation. --- docs/quickstart.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b444e080..749a1f75 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -538,16 +538,16 @@ The Request Object `````````````````` The request object is documented in the API section and we will not cover -it here in detail (see :class:`~flask.request`). Here is a broad overview of +it here in detail (see :class:`~flask.Request`). Here is a broad overview of some of the most common operations. First of all you have to import it from the ``flask`` module:: from flask import request The current request method is available by using the -:attr:`~flask.request.method` attribute. To access form data (data +:attr:`~flask.Request.method` attribute. To access form data (data transmitted in a ``POST`` or ``PUT`` request) you can use the -:attr:`~flask.request.form` attribute. Here is a full example of the two +:attr:`~flask.Request.form` attribute. Here is a full example of the two attributes mentioned above:: @app.route('/login', methods=['POST', 'GET']) @@ -570,7 +570,7 @@ error page is shown instead. So for many situations you don't have to deal with that problem. To access parameters submitted in the URL (``?key=value``) you can use the -:attr:`~flask.request.args` attribute:: +:attr:`~flask.Request.args` attribute:: searchword = request.args.get('key', '') @@ -579,7 +579,7 @@ We recommend accessing URL parameters with `get` or by catching the bad request page in that case is not user friendly. For a full list of methods and attributes of the request object, head over -to the :class:`~flask.request` documentation. +to the :class:`~flask.Request` documentation. File Uploads From 3fc8be5a4e9c1db58dcc74d3d48bf70b6a8db932 Mon Sep 17 00:00:00 2001 From: Kim Blomqvist Date: Tue, 17 Jan 2017 17:15:51 +0200 Subject: [PATCH 303/440] Disable debug when FLASK_DEBUG=False (#2155) Convert FLASK_DEBUG envvar to lower before test if in tuple --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index c6c2cddc..2f446327 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -58,7 +58,7 @@ def get_debug_flag(default=None): val = os.environ.get('FLASK_DEBUG') if not val: return default - return val not in ('0', 'false', 'no') + return val.lower() not in ('0', 'false', 'no') def _endpoint_from_view_func(view_func): From fe7910ccd59242af106fcc67cfd47e6e2355787d Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Tue, 17 Jan 2017 11:20:07 -0800 Subject: [PATCH 304/440] Update docs that request is an object, not a class (#2154) Cleanup sphinx formatting to show that `request` is an object, not a class. The actual class name is `Request`. Based on discussion [here](https://github.com/pallets/flask/pull/2151#issuecomment-272699147). --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 7da28dc8..b5009907 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -66,7 +66,7 @@ Incoming Request Data ============= ====================================================== -.. class:: request +.. attribute:: request To access incoming request data, you can use the global `request` object. Flask parses incoming request data for you and gives you From 1636a4c410a1bf3713bc1da8d4de7ca66cdf1681 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Tue, 17 Jan 2017 13:22:16 -0800 Subject: [PATCH 305/440] use SERVER_NAME to set host and port in app.run() (#2152) --- flask/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/flask/app.py b/flask/app.py index 942992dc..92ee8772 100644 --- a/flask/app.py +++ b/flask/app.py @@ -825,14 +825,14 @@ class Flask(_PackageBoundObject): information. """ from werkzeug.serving import run_simple - if host is None: - host = '127.0.0.1' - if port is None: - server_name = self.config['SERVER_NAME'] - if server_name and ':' in server_name: - port = int(server_name.rsplit(':', 1)[1]) - else: - port = 5000 + _host = '127.0.0.1' + _port = 5000 + server_name = self.config.get("SERVER_NAME") + sn_host, sn_port = None, None + if server_name: + sn_host, _, sn_port = server_name.partition(':') + host = host or sn_host or _host + port = int(port or sn_port or _port) if debug is not None: self.debug = bool(debug) options.setdefault('use_reloader', self.debug) From 42fbbb4cbbd312464214dd66d0985828c16dce67 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 17 Jan 2017 14:08:33 -0800 Subject: [PATCH 306/440] add test and changelog for SERVER_NAME app.run default ref #2152 --- CHANGES | 2 ++ tests/test_basic.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGES b/CHANGES index b096b0fe..157e92e3 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Bugfix release, unreleased within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. +- Use the``SERVER_NAME`` config if it is present as default values for + ``app.run``. ``#2109``, ``#2152`` Version 0.12 ------------ diff --git a/tests/test_basic.py b/tests/test_basic.py index a099c904..6341234b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1681,3 +1681,20 @@ def test_run_server_port(monkeypatch): hostname, port = 'localhost', 8000 app.run(hostname, port, debug=True) assert rv['result'] == 'running on %s:%s ...' % (hostname, port) + + +@pytest.mark.parametrize('host,port,expect_host,expect_port', ( + (None, None, 'pocoo.org', 8080), + ('localhost', None, 'localhost', 8080), + (None, 80, 'pocoo.org', 80), + ('localhost', 80, 'localhost', 80), +)) +def test_run_from_config(monkeypatch, host, port, expect_host, expect_port): + def run_simple_mock(hostname, port, *args, **kwargs): + assert hostname == expect_host + assert port == expect_port + + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'pocoo.org:8080' + app.run(host, port) From c9b33d0e860e347f1ed46eebadbfef4f5422b6da Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 Jan 2017 12:26:52 +0100 Subject: [PATCH 307/440] Convert Flask.run into a noop when run from the CLI --- CHANGES | 4 ++++ flask/app.py | 7 +++++++ flask/cli.py | 7 +++++++ flask/debughelpers.py | 12 ++++++++++++ 4 files changed, 30 insertions(+) diff --git a/CHANGES b/CHANGES index 157e92e3..62bb2004 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ Version 0.13 Major release, unreleased +- Make `app.run()` into a noop if a Flask application is run from the + development server on the command line. This avoids some behavior that + was confusing to debug for newcomers. + Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 92ee8772..27918d01 100644 --- a/flask/app.py +++ b/flask/app.py @@ -824,6 +824,13 @@ class Flask(_PackageBoundObject): :func:`werkzeug.serving.run_simple` for more information. """ + # Change this into a no-op if the server is invoked from the + # command line. Have a look at cli.py for more information. + if os.environ.get('FLASK_RUN_FROM_CLI_SERVER') == '1': + from .debughelpers import explain_ignored_app_run + explain_ignored_app_run() + return + from werkzeug.serving import run_simple _host = '127.0.0.1' _port = 5000 diff --git a/flask/cli.py b/flask/cli.py index 074ee768..bde5a13b 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -412,6 +412,13 @@ def run_command(info, host, port, reload, debugger, eager_loading, """ from werkzeug.serving import run_simple + # Set a global flag that indicates that we were invoked from the + # command line interface provided server command. This is detected + # by Flask.run to make the call into a no-op. This is necessary to + # avoid ugly errors when the script that is loaded here also attempts + # to start a server. + os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1' + debug = get_debug_flag() if reload is None: reload = bool(debug) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 90710dd3..9e44fe69 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -8,6 +8,9 @@ :copyright: (c) 2015 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import os +from warnings import warn + from ._compat import implements_to_string, text_type from .app import Flask from .blueprints import Blueprint @@ -153,3 +156,12 @@ def explain_template_loading_attempts(app, template, attempts): info.append(' See http://flask.pocoo.org/docs/blueprints/#templates') app.logger.info('\n'.join(info)) + + +def explain_ignored_app_run(): + if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': + warn(Warning('Silently ignoring app.run() because the ' + 'application is run from the flask command line ' + 'executable. Consider putting app.run() behind an ' + 'if __name__ == "__main__" guard to silence this ' + 'warning.'), stacklevel=3) From f84fdadda9f675a3e94a042790e010be07927af4 Mon Sep 17 00:00:00 2001 From: Swan Htet Aung Date: Thu, 9 Feb 2017 18:01:12 +0630 Subject: [PATCH 308/440] Update 4.4.3 HTTP Methods Example Otherwise it produces `ValueError: View function did not return a response`. --- docs/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 749a1f75..b619185d 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -306,9 +306,9 @@ can be changed by providing the ``methods`` argument to the @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, ``HEAD`` will be added automatically for you. You don't have to deal with that. It will also make sure that ``HEAD`` requests From 95db82f8f7df0acce7051a8dedf29b88e436f5ab Mon Sep 17 00:00:00 2001 From: vojtekb Date: Thu, 9 Feb 2017 18:34:16 +0100 Subject: [PATCH 309/440] py.test => pytest (#2173) py.test => pytest --- CONTRIBUTING.rst | 10 +++++----- README | 4 ++-- docs/tutorial/testing.rst | 2 +- setup.cfg | 2 +- tox.ini | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d9cd2214..1c9c8912 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -38,7 +38,7 @@ Running the testsuite You probably want to set up a `virtualenv `_. -The minimal requirement for running the testsuite is ``py.test``. You can +The minimal requirement for running the testsuite is ``pytest``. You can install it with:: pip install pytest @@ -54,9 +54,9 @@ Install Flask as an editable package using the current source:: Then you can run the testsuite with:: - py.test + pytest -With only py.test installed, a large part of the testsuite will get skipped +With only pytest installed, a large part of the testsuite will get skipped though. Whether this is relevant depends on which part of Flask you're working on. Travis is set up to run the full testsuite when you submit your pull request anyways. @@ -79,11 +79,11 @@ plugin. This assumes you have already run the testsuite (see previous section): After this has been installed, you can output a report to the command line using this command:: - py.test --cov=flask tests/ + pytest --cov=flask tests/ Generate a HTML report can be done using this command:: - py.test --cov-report html --cov=flask tests/ + pytest --cov-report html --cov=flask tests/ Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io diff --git a/README b/README index baea6b24..75c5e7b1 100644 --- a/README +++ b/README @@ -33,9 +33,9 @@ Good that you're asking. The tests are in the tests/ folder. To run the tests use the - `py.test` testing tool: + `pytest` testing tool: - $ py.test + $ pytest Details on contributing can be found in CONTRIBUTING.rst diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index dcf36594..26099375 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -46,7 +46,7 @@ At this point you can run the tests. Here ``pytest`` will be used. Run and watch the tests pass, within the top-level :file:`flaskr/` directory as:: - py.test + pytest Testing + setuptools -------------------- diff --git a/setup.cfg b/setup.cfg index cd282e48..0e97b0ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ universal = 1 license_file = LICENSE [tool:pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs +norecursedirs = .* *.egg *.egg-info env* artwork docs examples diff --git a/tox.ini b/tox.ini index 8d5a0f43..764b4030 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ commands = pip install -e examples/flaskr pip install -e examples/minitwit pip install -e examples/patterns/largerapp - py.test --cov=flask --cov-report html [] + pytest --cov=flask --cov-report html [] deps= pytest pytest-cov From 89798ea7dd549ba8e06112e3b44b0cb7d9d4f417 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 9 Feb 2017 18:35:21 +0100 Subject: [PATCH 310/440] Remove examples dir again --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0e97b0ba..cd282e48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ universal = 1 license_file = LICENSE [tool:pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs examples +norecursedirs = .* *.egg *.egg-info env* artwork docs From 5efb1632371048dcb5cd622b6d597c6b69b901b5 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Fri, 10 Feb 2017 03:19:59 -0800 Subject: [PATCH 311/440] bdist_wheel replaces wheel (#2179) https://packaging.python.org/distributing/#universal-wheels --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index cd282e48..781de592 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [aliases] release = egg_info -RDb '' -[wheel] +[bdist_wheel] universal = 1 [metadata] From bb0e755c808a8541192982ba7b86308b68ff7657 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Sat, 11 Feb 2017 01:43:11 -0800 Subject: [PATCH 312/440] Migrate various docs links to https (#2180) Also fixed a few outdated links --- CONTRIBUTING.rst | 2 +- docs/_templates/sidebarintro.html | 6 +++--- docs/conf.py | 2 +- docs/deploying/fastcgi.rst | 6 +++--- docs/deploying/index.rst | 2 +- docs/deploying/mod_wsgi.rst | 8 ++++---- docs/deploying/uwsgi.rst | 4 ++-- docs/errorhandling.rst | 4 ++-- docs/extensiondev.rst | 2 +- docs/installation.rst | 2 +- docs/patterns/appfactories.rst | 2 +- docs/patterns/favicon.rst | 2 +- docs/patterns/fileuploads.rst | 2 +- docs/patterns/sqlalchemy.rst | 6 +++--- docs/patterns/sqlite3.rst | 14 +++++++------- docs/patterns/wtforms.rst | 2 +- docs/quickstart.rst | 8 ++++---- docs/security.rst | 2 +- docs/tutorial/introduction.rst | 2 +- docs/upgrading.rst | 2 +- examples/minitwit/minitwit/minitwit.py | 2 +- setup.py | 4 ++-- 22 files changed, 43 insertions(+), 43 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1c9c8912..66766512 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -28,7 +28,7 @@ Submitting patches clearly under which circumstances the bug happens. Make sure the test fails without your patch. -- Try to follow `PEP8 `_, but you +- Try to follow `PEP8 `_, but you may ignore the line-length-limit if following it would make the code uglier. diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index ec1608fd..71fcd73b 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -16,7 +16,7 @@

      Useful Links

      diff --git a/docs/conf.py b/docs/conf.py index 81106a3a..8682dd8c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -260,7 +260,7 @@ intersphinx_mapping = { 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None), 'click': ('http://click.pocoo.org/', None), 'jinja': ('http://jinja.pocoo.org/docs/', None), - 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None), + 'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None), 'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None), 'blinker': ('https://pythonhosted.org/blinker/', None) } diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index efae5163..5ca2a084 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -159,7 +159,7 @@ work in the URL root you have to work around a lighttpd bug with the Make sure to apply it only if you are mounting the application the URL root. Also, see the Lighty docs for more information on `FastCGI and Python -`_ (note that +`_ (note that explicitly passing a socket to run() is no longer necessary). Configuring nginx @@ -234,7 +234,7 @@ python path. Common problems are: web server. - Different python interpreters being used. -.. _nginx: http://nginx.org/ -.. _lighttpd: http://www.lighttpd.net/ +.. _nginx: https://nginx.org/ +.. _lighttpd: https://www.lighttpd.net/ .. _cherokee: http://cherokee-project.com/ .. _flup: https://pypi.python.org/pypi/flup diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 20e71762..6950e47a 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -21,7 +21,7 @@ Hosted options - `Deploying Flask on OpenShift `_ - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ -- `Deploying Flask on AWS Elastic Beanstalk `_ +- `Deploying Flask on AWS Elastic Beanstalk `_ - `Sharing your Localhost Server with Localtunnel `_ - `Deploying on Azure (IIS) `_ - `Deploying on PythonAnywhere `_ diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index 0f4af6c3..ca694b7d 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -13,7 +13,7 @@ If you are using the `Apache`_ webserver, consider using `mod_wsgi`_. not called because this will always start a local WSGI server which we do not want if we deploy that application to mod_wsgi. -.. _Apache: http://httpd.apache.org/ +.. _Apache: https://httpd.apache.org/ Installing `mod_wsgi` --------------------- @@ -114,7 +114,7 @@ refuse to run with the above configuration. On a Windows system, eliminate those Note: There have been some changes in access control configuration for `Apache 2.4`_. -.. _Apache 2.4: http://httpd.apache.org/docs/trunk/upgrading.html +.. _Apache 2.4: https://httpd.apache.org/docs/trunk/upgrading.html Most notably, the syntax for directory permissions has changed from httpd 2.2 @@ -133,9 +133,9 @@ to httpd 2.4 syntax For more information consult the `mod_wsgi documentation`_. .. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi -.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html +.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html .. _virtual python: https://pypi.python.org/pypi/virtualenv -.. _mod_wsgi documentation: http://modwsgi.readthedocs.io/en/develop/index.html +.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html Troubleshooting --------------- diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index fc991e72..50c85fb2 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -66,7 +66,7 @@ to have it in the URL root its a bit simpler:: uwsgi_pass unix:/tmp/yourapplication.sock; } -.. _nginx: http://nginx.org/ -.. _lighttpd: http://www.lighttpd.net/ +.. _nginx: https://nginx.org/ +.. _lighttpd: https://www.lighttpd.net/ .. _cherokee: http://cherokee-project.com/ .. _uwsgi: http://projects.unbit.it/uwsgi/ diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 64c0f8b3..2791fec3 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -34,7 +34,7 @@ Error Logging Tools Sending error mails, even if just for critical ones, can become overwhelming if enough users are hitting the error and log files are typically never looked at. This is why we recommend using `Sentry -`_ for dealing with application errors. It's +`_ for dealing with application errors. It's available as an Open Source project `on GitHub `__ and is also available as a `hosted version `_ which you can try for free. Sentry @@ -89,7 +89,7 @@ Register error handlers using :meth:`~flask.Flask.errorhandler` or @app.errorhandler(werkzeug.exceptions.BadRequest) def handle_bad_request(e): return 'bad request!' - + app.register_error_handler(400, lambda e: 'bad request!') Those two ways are equivalent, but the first one is more clear and leaves diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index c9a72094..9ae6e6f1 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -405,6 +405,6 @@ schema. The ``flask.ext.foo`` compatibility alias is still in Flask 0.11 but is now deprecated -- you should use ``flask_foo``. -.. _OAuth extension: http://pythonhosted.org/Flask-OAuth/ +.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/ .. _mailinglist: http://flask.pocoo.org/mailinglist/ .. _IRC channel: http://flask.pocoo.org/community/irc/ diff --git a/docs/installation.rst b/docs/installation.rst index 6f833eac..96c363f5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -112,7 +112,7 @@ it to operate on a git checkout. Either way, virtualenv is recommended. Get the git checkout in a new virtualenv and run in development mode:: - $ git clone http://github.com/pallets/flask.git + $ git clone https://github.com/pallets/flask.git Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask $ virtualenv venv diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index c118a273..fdbde504 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -60,7 +60,7 @@ Factories & Extensions It's preferable to create your extensions and app factories so that the extension object does not initially get bound to the application. -Using `Flask-SQLAlchemy `_, +Using `Flask-SQLAlchemy `_, as an example, you should not do something along those lines:: def create_app(config_filename): diff --git a/docs/patterns/favicon.rst b/docs/patterns/favicon.rst index acdee24b..21ea767f 100644 --- a/docs/patterns/favicon.rst +++ b/docs/patterns/favicon.rst @@ -49,5 +49,5 @@ web server's documentation. See also -------- -* The `Favicon `_ article on +* The `Favicon `_ article on Wikipedia diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 8ab8c033..dc3820be 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -181,4 +181,4 @@ applications dealing with uploads, there is also a Flask extension called blacklisting of extensions and more. .. _jQuery: https://jquery.com/ -.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/ +.. _Flask-Uploads: https://pythonhosted.org/Flask-Uploads/ diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 9c985cc6..8785a6e2 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -108,9 +108,9 @@ Querying is simple as well: >>> User.query.filter(User.name == 'admin').first() -.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _SQLAlchemy: https://www.sqlalchemy.org/ .. _declarative: - http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/ + https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/ Manual Object Relational Mapping -------------------------------- @@ -215,4 +215,4 @@ You can also pass strings of SQL statements to the (1, u'admin', u'admin@localhost') For more information about SQLAlchemy, head over to the -`website `_. +`website `_. diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 66a7c4c4..15f38ea7 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -3,8 +3,8 @@ Using SQLite 3 with Flask ========================= -In Flask you can easily implement the opening of database connections on -demand and closing them when the context dies (usually at the end of the +In Flask you can easily implement the opening of database connections on +demand and closing them when the context dies (usually at the end of the request). Here is a simple example of how you can use SQLite 3 with Flask:: @@ -71,7 +71,7 @@ Now in each request handling function you can access `g.db` to get the current open database connection. To simplify working with SQLite, a row factory function is useful. It is executed for every result returned from the database to convert the result. For instance, in order to get -dictionaries instead of tuples, this could be inserted into the ``get_db`` +dictionaries instead of tuples, this could be inserted into the ``get_db`` function we created above:: def make_dicts(cursor, row): @@ -102,15 +102,15 @@ This would use Row objects rather than dicts to return the results of queries. T Additionally, it is a good idea to provide a query function that combines getting the cursor, executing and fetching the results:: - + def query_db(query, args=(), one=False): cur = get_db().execute(query, args) rv = cur.fetchall() cur.close() return (rv[0] if rv else None) if one else rv -This handy little function, in combination with a row factory, makes -working with the database much more pleasant than it is by just using the +This handy little function, in combination with a row factory, makes +working with the database much more pleasant than it is by just using the raw cursor and connection objects. Here is how you can use it:: @@ -131,7 +131,7 @@ To pass variable parts to the SQL statement, use a question mark in the statement and pass in the arguments as a list. Never directly add them to the SQL statement with string formatting because this makes it possible to attack the application using `SQL Injections -`_. +`_. Initial Schemas --------------- diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 2649cad6..0e53de17 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -19,7 +19,7 @@ forms. fun. You can get it from `PyPI `_. -.. _Flask-WTF: http://pythonhosted.org/Flask-WTF/ +.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/stable/ The Forms --------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b619185d..7ce8a90f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -102,9 +102,9 @@ docs to see the alternative method for running a server. Invalid Import Name ``````````````````` -The ``FLASK_APP`` environment variable is the name of the module to import at -:command:`flask run`. In case that module is incorrectly named you will get an -import error upon start (or if debug is enabled when you navigate to the +The ``FLASK_APP`` environment variable is the name of the module to import at +:command:`flask run`. In case that module is incorrectly named you will get an +import error upon start (or if debug is enabled when you navigate to the application). It will tell you what it tried to import and why it failed. The most common reason is a typo or because you did not actually create an @@ -367,7 +367,7 @@ HTTP has become quite popular lately and browsers are no longer the only clients that are using HTTP. For instance, many revision control systems use it. -.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt +.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt Static Files ------------ diff --git a/docs/security.rst b/docs/security.rst index 587bd4ef..ad0d1244 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -15,7 +15,7 @@ it JavaScript) into the context of a website. To remedy this, developers have to properly escape text so that it cannot include arbitrary HTML tags. For more information on that have a look at the Wikipedia article on `Cross-Site Scripting -`_. +`_. Flask configures Jinja2 to automatically escape all values unless explicitly told otherwise. This should rule out all XSS problems caused diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index dd46628b..1abe597f 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -31,4 +31,4 @@ Here a screenshot of the final application: Continue with :ref:`tutorial-folders`. -.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _SQLAlchemy: https://www.sqlalchemy.org/ diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 41b70f03..436b0430 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -143,7 +143,7 @@ when there is no request context yet but an application context. The old ``flask.Flask.request_globals_class`` attribute was renamed to :attr:`flask.Flask.app_ctx_globals_class`. -.. _Flask-OldSessions: http://pythonhosted.org/Flask-OldSessions/ +.. _Flask-OldSessions: https://pythonhosted.org/Flask-OldSessions/ Version 0.9 ----------- diff --git a/examples/minitwit/minitwit/minitwit.py b/examples/minitwit/minitwit/minitwit.py index bbc3b483..69840267 100644 --- a/examples/minitwit/minitwit/minitwit.py +++ b/examples/minitwit/minitwit/minitwit.py @@ -85,7 +85,7 @@ def format_datetime(timestamp): def gravatar_url(email, size=80): """Return the gravatar image for the given email address.""" - return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ + return 'https://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ (md5(email.strip().lower().encode('utf-8')).hexdigest(), size) diff --git a/setup.py b/setup.py index 983f7611..08995073 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ Links * `website `_ * `documentation `_ * `development version - `_ + `_ """ import re @@ -59,7 +59,7 @@ with open('flask/__init__.py', 'rb') as f: setup( name='Flask', version=version, - url='http://github.com/pallets/flask/', + url='https://github.com/pallets/flask/', license='BSD', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', From eaba4a73aa1013db908ea07af6028056c4fde706 Mon Sep 17 00:00:00 2001 From: Nick Ficano Date: Wed, 15 Feb 2017 11:55:56 -0500 Subject: [PATCH 313/440] Fix typo in file header (jsonimpl => json) --- flask/json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/json.py b/flask/json.py index 16e0c295..19b337c3 100644 --- a/flask/json.py +++ b/flask/json.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - flask.jsonimpl - ~~~~~~~~~~~~~~ + flask.json + ~~~~~~~~~~ Implementation helpers for the JSON support in Flask. From dc5f48f587a93d1f921d9d7e06fa05fdea6151d9 Mon Sep 17 00:00:00 2001 From: Timothy John Perisho Eccleston Date: Sat, 18 Feb 2017 00:41:58 -0600 Subject: [PATCH 314/440] Fix typo in docs/tutorial/templates.rst (#2186) --- docs/tutorial/templates.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 269e8df1..4cb7db7f 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -59,7 +59,7 @@ show_entries.html This template extends the :file:`layout.html` template from above to display the messages. Note that the ``for`` loop iterates over the messages we passed in with the :func:`~flask.render_template` function. Notice that the form is -configured to to submit to the `add_entry` view function and use ``POST`` as +configured to submit to the `add_entry` view function and use ``POST`` as HTTP method: .. sourcecode:: html+jinja From af11098057b022c172c1142f02e50cc3c4b065b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Di=CC=81az=20Sa=CC=81nchez?= Date: Tue, 28 Feb 2017 00:13:45 +0100 Subject: [PATCH 315/440] Updated documentation for being able to use init_db method --- docs/testing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 0737936e..f4ee64ec 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -33,7 +33,7 @@ In order to test the application, we add a second module (:file:`flaskr_tests.py`) and create a unittest skeleton there:: import os - import flaskr + from flaskr import flaskr import unittest import tempfile @@ -208,7 +208,7 @@ temporarily. With this you can access the :class:`~flask.request`, functions. Here is a full example that demonstrates this approach:: import flask - + app = flask.Flask(__name__) with app.test_request_context('/?name=Peter'): From fca5577a0097e876e8f8e8d2f3961a40fa44bbeb Mon Sep 17 00:00:00 2001 From: Sebastian Kalinowski Date: Tue, 28 Feb 2017 06:05:09 +0100 Subject: [PATCH 316/440] Remove extra HTML tag from fileupload docs (#2141) --- docs/patterns/fileuploads.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index dc3820be..8bf2287c 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -72,8 +72,8 @@ the file and redirects the user to the URL for the uploaded file:: Upload new File

      Upload new File

      -

      - + +

      ''' From c43560777a3efeaeaf0eb47568171f04103dc363 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Sat, 4 Mar 2017 18:29:04 +0800 Subject: [PATCH 317/440] Add tips for debug config with flask cli (#2196) * Add tips for debug config with flask cli `app.debug` and `app.config['DEBUG']` are not compatible with the `flask` script. * Grammar fix * Grammar fix --- docs/config.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 6d37c1e8..c36cc852 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -44,6 +44,21 @@ method:: SECRET_KEY='...' ) +.. 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:: + + $ export FLASK_DEBUG=1 + $ flask run + + (On Windows you need to use ``set`` instead of ``export``). + + ``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 ---------------------------- @@ -52,7 +67,8 @@ The following configuration values are used internally by Flask: .. tabularcolumns:: |p{6.5cm}|p{8.5cm}| ================================= ========================================= -``DEBUG`` enable/disable debug mode +``DEBUG`` enable/disable debug mode when using + ``Flask.run()`` method to start server ``TESTING`` enable/disable testing mode ``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the propagation of exceptions. If not set or From d9a28434af3c946c79d062d7474ace42ef85d798 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 4 Mar 2017 22:32:23 +0100 Subject: [PATCH 318/440] Fix typo --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 62bb2004..2c0a4869 100644 --- a/CHANGES +++ b/CHANGES @@ -21,7 +21,7 @@ Bugfix release, unreleased within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. -- Use the``SERVER_NAME`` config if it is present as default values for +- Use the ``SERVER_NAME`` config if it is present as default values for ``app.run``. ``#2109``, ``#2152`` Version 0.12 From 06112a555a9398701d3269253355c214791e1eca Mon Sep 17 00:00:00 2001 From: Elton Law Date: Sun, 5 Mar 2017 07:07:49 -0500 Subject: [PATCH 319/440] Close
    • tag in tutorial (#2199) Change was merged in the example code but wasn't changed in the docs. https://github.com/pallets/flask/commit/c54d67adee6f99113f525b376da4af27c3001321 --- docs/tutorial/templates.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 4cb7db7f..4f7977e8 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -79,9 +79,9 @@ HTTP method: {% endif %}
        {% for entry in entries %} -
      • {{ entry.title }}

        {{ entry.text|safe }} +
      • {{ entry.title }}

        {{ entry.text|safe }}
      • {% else %} -
      • Unbelievable. No entries here so far +
      • Unbelievable. No entries here so far
      • {% endfor %}
      {% endblock %} From f5adb61b28f240effbba5a4686647c2af6e85b94 Mon Sep 17 00:00:00 2001 From: Static Date: Mon, 6 Mar 2017 07:05:59 -0600 Subject: [PATCH 320/440] Fix typos/grammar in docs (#2201) --- docs/conf.py | 2 +- docs/patterns/fileuploads.rst | 2 +- docs/styleguide.rst | 2 +- docs/upgrading.rst | 4 ++-- flask/app.py | 4 ++-- scripts/flask-07-upgrade.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8682dd8c..f53d72fa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -234,7 +234,7 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf'] # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' -# The unique identifier of the text. This can be a ISBN number +# The unique identifier of the text. This can be an ISBN number # or the project homepage. #epub_identifier = '' diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 8bf2287c..3a42d325 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -58,7 +58,7 @@ the file and redirects the user to the URL for the uploaded file:: return redirect(request.url) file = request.files['file'] # if user does not select file, browser also - # submit a empty part without filename + # submit an empty part without filename if file.filename == '': flash('No selected file') return redirect(request.url) diff --git a/docs/styleguide.rst b/docs/styleguide.rst index e03e4ef5..390d5668 100644 --- a/docs/styleguide.rst +++ b/docs/styleguide.rst @@ -167,7 +167,7 @@ Docstring conventions: """ Module header: - The module header consists of an utf-8 encoding declaration (if non + The module header consists of a utf-8 encoding declaration (if non ASCII letters are used, but it is recommended all the time) and a standard docstring:: diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 436b0430..af2383c0 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -49,7 +49,7 @@ Any of the following is functionally equivalent:: response = send_file(open(fname), attachment_filename=fname) response.set_etag(...) -The reason for this is that some file-like objects have a invalid or even +The reason for this is that some file-like objects have an invalid or even misleading ``name`` attribute. Silently swallowing errors in such cases was not a satisfying solution. @@ -198,7 +198,7 @@ applications with Flask. Because we want to make upgrading as easy as possible we tried to counter the problems arising from these changes by providing a script that can ease the transition. -The script scans your whole application and generates an unified diff with +The script scans your whole application and generates a unified diff with changes it assumes are safe to apply. However as this is an automated tool it won't be able to find all use cases and it might miss some. We internally spread a lot of deprecation warnings all over the place to make diff --git a/flask/app.py b/flask/app.py index 27918d01..7745ace6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -391,7 +391,7 @@ class Flask(_PackageBoundObject): #: is the class for the instance check and the second the error handler #: function. #: - #: To register a error handler, use the :meth:`errorhandler` + #: To register an error handler, use the :meth:`errorhandler` #: decorator. self.error_handler_spec = {None: self._error_handlers} @@ -1354,7 +1354,7 @@ class Flask(_PackageBoundObject): will have to surround the execution of these code by try/except statements and log occurring errors. - When a teardown function was called because of a exception it will + When a teardown function was called because of an exception it will be passed an error object. The return values of teardown functions are ignored. diff --git a/scripts/flask-07-upgrade.py b/scripts/flask-07-upgrade.py index 7fbdd49c..18e1a14b 100644 --- a/scripts/flask-07-upgrade.py +++ b/scripts/flask-07-upgrade.py @@ -5,7 +5,7 @@ ~~~~~~~~~~~~~~~~ This command line script scans a whole application tree and attempts to - output an unified diff with all the changes that are necessary to easily + output a unified diff with all the changes that are necessary to easily upgrade the application to 0.7 and to not yield deprecation warnings. This will also attempt to find `after_request` functions that don't modify From a7f1a21c1204828388eaed1e3903a74c904c8147 Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Tue, 7 Mar 2017 10:09:46 +0900 Subject: [PATCH 321/440] Don't rely on X-Requested-With for pretty print json response (#2193) * Don't rely on X-Requested-With for pretty print json response * Fix test cases for pretty print json patch * Fix gramma error in docs for pretty print json config * Add changelog for JSONIFY_PRETTYPRINT_REGULAR --- CHANGES | 3 +++ docs/config.rst | 8 +++----- flask/app.py | 2 +- flask/json.py | 2 +- tests/test_basic.py | 2 +- tests/test_helpers.py | 2 ++ 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 2c0a4869..7c50d0c7 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,9 @@ Major release, unreleased - Make `app.run()` into a noop if a Flask application is run from the development server on the command line. This avoids some behavior that 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. Version 0.12.1 -------------- diff --git a/docs/config.rst b/docs/config.rst index c36cc852..75ce239a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -194,11 +194,9 @@ The following configuration values are used internally by Flask: This is not recommended but might give you a performance improvement on the cost of cacheability. -``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) - jsonify responses will be pretty printed - if they are not requested by an - XMLHttpRequest object (controlled by - the ``X-Requested-With`` header) +``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` or the Flask app + is running in debug mode, jsonify responses + will be pretty printed. ``JSONIFY_MIMETYPE`` MIME type used for jsonify responses. ``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of the template source and reload it diff --git a/flask/app.py b/flask/app.py index 7745ace6..bc38d9ea 100644 --- a/flask/app.py +++ b/flask/app.py @@ -314,7 +314,7 @@ class Flask(_PackageBoundObject): 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, - 'JSONIFY_PRETTYPRINT_REGULAR': True, + 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }) diff --git a/flask/json.py b/flask/json.py index 19b337c3..0ba9d717 100644 --- a/flask/json.py +++ b/flask/json.py @@ -248,7 +248,7 @@ def jsonify(*args, **kwargs): indent = None separators = (',', ':') - if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_xhr: + if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug: indent = 2 separators = (', ', ': ') diff --git a/tests/test_basic.py b/tests/test_basic.py index 6341234b..942eb0f6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -995,7 +995,7 @@ def test_make_response_with_response_instance(): rv = flask.make_response( flask.jsonify({'msg': 'W00t'}), 400) assert rv.status_code == 400 - assert rv.data == b'{\n "msg": "W00t"\n}\n' + assert rv.data == b'{"msg":"W00t"}\n' assert rv.mimetype == 'application/json' rv = flask.make_response( diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3e2ea8cd..fd448fb8 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -289,6 +289,8 @@ class TestJSON(object): def test_json_key_sorting(self): app = flask.Flask(__name__) app.testing = True + app.debug = True + assert app.config['JSON_SORT_KEYS'] == True d = dict.fromkeys(range(20), 'foo') From 7a5e8ef38e0f4f110b7739253308a3356b13b0de Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 8 Mar 2017 11:26:38 -0800 Subject: [PATCH 322/440] Fix broken link (#2202) --- docs/patterns/distribute.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index 72cc25d6..f4a07579 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -174,4 +174,4 @@ the code without having to run ``install`` again after each change. .. _pip: https://pypi.python.org/pypi/pip -.. _Setuptools: https://pythonhosted.org/setuptools +.. _Setuptools: https://pypi.python.org/pypi/setuptools From 46e8427d814589145ffcdc10cce45b791bde795b Mon Sep 17 00:00:00 2001 From: John Bodley Date: Sat, 11 Mar 2017 09:59:34 -0800 Subject: [PATCH 323/440] Document run() host defaulting to SERVER_NAME --- flask/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index bc38d9ea..c8540b5f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -813,7 +813,8 @@ class Flask(_PackageBoundObject): :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to have the server available externally as well. Defaults to - ``'127.0.0.1'``. + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config + variable if present. :param port: the port of the webserver. Defaults to ``5000`` or the port defined in the ``SERVER_NAME`` config variable if present. From 1add1f8a02976e070660d9ae0b877bc3f8a36e86 Mon Sep 17 00:00:00 2001 From: Jan Ferko Date: Mon, 13 Mar 2017 13:58:24 +0100 Subject: [PATCH 324/440] Use print function in quickstart (#2204) Example in URL Building section uses `print` statement instead of `print` function, which causes syntax error when example is run on Python 3. --- docs/quickstart.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 7ce8a90f..09365496 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -264,10 +264,10 @@ some examples:: ... def profile(username): pass ... >>> with app.test_request_context(): - ... print url_for('index') - ... print url_for('login') - ... print url_for('login', next='/') - ... print url_for('profile', username='John Doe') + ... print(url_for('index')) + ... print(url_for('login')) + ... print(url_for('login', next='/')) + ... print(url_for('profile', username='John Doe')) ... / /login From 5b7fd9ad889e54d4d694d310b559c921d7df75cf Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 16 Mar 2017 14:37:58 +0100 Subject: [PATCH 325/440] Print a stacktrace on CLI error (closes #2208) --- flask/cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flask/cli.py b/flask/cli.py index bde5a13b..8f8fac03 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -11,6 +11,7 @@ import os import sys +import traceback from threading import Lock, Thread from functools import update_wrapper @@ -368,6 +369,9 @@ class FlaskGroup(AppGroup): # want the help page to break if the app does not exist. # If someone attempts to use the command we try to create # the app again and this will give us the error. + # However, we will not do so silently because that would confuse + # users. + traceback.print_exc() pass return sorted(rv) From ad42d88fb2cda12a21c4fb6f002f425f233d1fe3 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 16 Mar 2017 14:42:09 +0100 Subject: [PATCH 326/440] Remove useless pass --- flask/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 8f8fac03..8db7e07e 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -372,7 +372,6 @@ class FlaskGroup(AppGroup): # However, we will not do so silently because that would confuse # users. traceback.print_exc() - pass return sorted(rv) def main(self, *args, **kwargs): From ed17bc171046a15f03c890687db8eb9652513bd9 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 16 Mar 2017 20:56:12 +0100 Subject: [PATCH 327/440] Add test to showcase that printing a traceback works --- tests/test_cli.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 313a34d2..82c69f93 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -191,3 +191,20 @@ def test_flaskgroup(): result = runner.invoke(cli, ['test']) assert result.exit_code == 0 assert result.output == 'flaskgroup\n' + + +def test_print_exceptions(): + """Print the stacktrace if the CLI.""" + def create_app(info): + raise Exception("oh no") + return Flask("flaskgroup") + + @click.group(cls=FlaskGroup, create_app=create_app) + def cli(**params): + pass + + runner = CliRunner() + result = runner.invoke(cli, ['--help']) + assert result.exit_code == 0 + assert 'Exception: oh no' in result.output + assert 'Traceback' in result.output From 2995366dde63b8acd1f246bcd5a4cf7d61f0c1fa Mon Sep 17 00:00:00 2001 From: Larivact Date: Fri, 17 Mar 2017 05:41:20 +0100 Subject: [PATCH 328/440] Clarify APPLICATION_ROOT #1714 --- docs/config.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 75ce239a..714b54c8 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -132,13 +132,13 @@ The following configuration values are used internally by Flask: by default enables URL generation without a request context but with an application context. -``APPLICATION_ROOT`` If the application does not occupy - a whole domain or subdomain this can - be set to the path where the application - is configured to live. This is for - session cookie as path value. If - domains are used, this should be - ``None``. +``APPLICATION_ROOT`` The path value used for the session + cookie if ``SESSION_COOKIE_PATH`` isn't + set. If it's also ``None`` ``'/'`` is used. + Note that to actually serve your Flask + app under a subpath you need to tell + your WSGI container the ``SCRIPT_NAME`` + WSGI environment variable. ``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will reject incoming requests with a content length greater than this by From 889c0ed1964f48557e532f95f9003c5ee6e2b727 Mon Sep 17 00:00:00 2001 From: Runar Trollet Kristoffersen Date: Sun, 19 Mar 2017 18:01:23 +0100 Subject: [PATCH 329/440] Issue #2212: documentation: virtualenv and python3 --- docs/installation.rst | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 96c363f5..38094ded 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -40,6 +40,12 @@ installations of Python, one for each project. It doesn't actually install separate copies of Python, but it does provide a clever way to keep different project environments isolated. Let's see how virtualenv works. + +.. admonition:: A note on python3 and virtualenv + + If you are planning on using python3 with the virtualenv, you don't need to + install ``virtualenv``. Python3 has built-in support for virtual environments. + If you are on Mac OS X or Linux, chances are that the following command will work for you:: @@ -55,24 +61,43 @@ install it first. Check the :ref:`windows-easy-install` section for more information about how to do that. Once you have it installed, run the same commands as above, but without the ``sudo`` prefix. +Creating a virtual environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Once you have virtualenv installed, just fire up a shell and create -your own environment. I usually create a project folder and a :file:`venv` +your own environment. I usually create a project folder and a :file:`virtenv` folder within:: $ mkdir myproject $ cd myproject - $ virtualenv venv - New python executable in venv/bin/python + +There is a little change in how you create a virtualenv depending on which python-version you are currently using. + +**Python2** + +:: + + $ virtualenv virtenv + New python executable in virtenv/bin/python Installing setuptools, pip............done. +**Python 3.6 and above** + +:: + + $ python3 -m venv virtenv + +Activating a virtual environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ . venv/bin/activate + $ . virtenv/bin/activate If you are a Windows user, the following command is for you:: - $ venv\Scripts\activate + $ virtenv\Scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). @@ -115,10 +140,10 @@ Get the git checkout in a new virtualenv and run in development mode:: $ git clone https://github.com/pallets/flask.git Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask - $ virtualenv venv - New python executable in venv/bin/python + $ virtualenv virtenv + New python executable in virtenv/bin/python Installing setuptools, pip............done. - $ . venv/bin/activate + $ . virtenv/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask From 7a7a163ff18c4491b8c2a6cd0630a6f4e4ce2984 Mon Sep 17 00:00:00 2001 From: Ed Brannin Date: Tue, 21 Mar 2017 15:22:15 -0400 Subject: [PATCH 330/440] shorten output when ImportError due to app bug. Before: ``` C:\dev\tmp>py -2 -m flask run Traceback (most recent call last): File "C:\Python27\lib\runpy.py", line 174, in _run_module_as_main "__main__", fname, loader, pkg_name) File "C:\Python27\lib\runpy.py", line 72, in _run_code exec code in run_globals File "c:\dev\sourcetree\flask\flask\__main__.py", line 15, in main(as_module=True) File "c:\dev\sourcetree\flask\flask\cli.py", line 523, in main cli.main(args=args, prog_name=name) File "c:\dev\sourcetree\flask\flask\cli.py", line 383, in main return AppGroup.main(self, *args, **kwargs) File "C:\Python27\lib\site-packages\click\core.py", line 697, in main rv = self.invoke(ctx) File "C:\Python27\lib\site-packages\click\core.py", line 1066, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) File "C:\Python27\lib\site-packages\click\core.py", line 895, in invoke return ctx.invoke(self.callback, **ctx.params) File "C:\Python27\lib\site-packages\click\core.py", line 535, in invoke return callback(*args, **kwargs) File "C:\Python27\lib\site-packages\click\decorators.py", line 64, in new_func return ctx.invoke(f, obj, *args[1:], **kwargs) File "C:\Python27\lib\site-packages\click\core.py", line 535, in invoke return callback(*args, **kwargs) File "c:\dev\sourcetree\flask\flask\cli.py", line 433, in run_command app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) File "c:\dev\sourcetree\flask\flask\cli.py", line 153, in __init__ self._load_unlocked() File "c:\dev\sourcetree\flask\flask\cli.py", line 177, in _load_unlocked self._app = rv = self.loader() File "c:\dev\sourcetree\flask\flask\cli.py", line 238, in load_app rv = locate_app(self.app_import_path) File "c:\dev\sourcetree\flask\flask\cli.py", line 91, in locate_app __import__(module) File "C:\dev\tmp\error.py", line 1, in import whatisthisidonteven ImportError: No module named whatisthisidonteven ``` After: ``` C:\dev\tmp>py -2 -m flask run Usage: python -m flask run [OPTIONS] Error: There was an error trying to import the app (error): Traceback (most recent call last): File "c:\dev\sourcetree\flask\flask\cli.py", line 91, in locate_app __import__(module) File "C:\dev\tmp\error.py", line 1, in import whatisthisidonteven ImportError: No module named whatisthisidonteven``` --- flask/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 8db7e07e..0cc240a2 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -93,7 +93,9 @@ def locate_app(app_id): # Reraise the ImportError if it occurred within the imported module. # Determine this by checking whether the trace has a depth > 1. if sys.exc_info()[-1].tb_next: - raise + stack_trace = traceback.format_exc() + raise NoAppException('There was an error trying to import' + ' the app (%s):\n%s' % (module, stack_trace)) else: raise NoAppException('The file/path provided (%s) does not appear' ' to exist. Please verify the path is ' From 6e5250ab5dcdbf1e6d47e8481ba80de4f44f20f9 Mon Sep 17 00:00:00 2001 From: Ed Brannin Date: Tue, 21 Mar 2017 16:17:09 -0400 Subject: [PATCH 331/440] Fix CLI test for ImportError -> NoAppException --- tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 82c69f93..8b291a63 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -83,7 +83,7 @@ def test_locate_app(test_apps): pytest.raises(NoAppException, locate_app, "notanpp.py") pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") - pytest.raises(ImportError, locate_app, "cliapp.importerrorapp") + pytest.raises(NoAppException, locate_app, "cliapp.importerrorapp") def test_find_default_import_path(test_apps, monkeypatch, tmpdir): From 0049922f2e690a6d58f335ca9196c95f1de84370 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 23 Mar 2017 17:30:48 +0100 Subject: [PATCH 332/440] Fix send_file to work with non-ascii filenames This commit implements https://tools.ietf.org/html/rfc2231#section-4 in order to support sending unicode characters. Tested on both Firefox and Chromium under Linux. This adds unidecode as a dependency, which might be relaxed by using .encode('latin-1', 'ignore') but wouldn't be as useful. Also, added a test for the correct headers to be added. Previously, using a filename parameter to send_file with unicode characters, it failed with the next error since HTTP headers don't allow non latin-1 characters. Error on request: Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 193, in run_wsgi execute(self.server.app) File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 186, in execute write(b'') File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 152, in write self.send_header(key, value) File "/usr/lib64/python3.6/http/server.py", line 509, in send_header ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict')) UnicodeEncodeError: 'latin-1' codec can't encode character '\uff0f' in position 58: ordinal not in range(256) Fixes #1286 --- flask/helpers.py | 6 +++++- setup.py | 1 + tests/test_helpers.py | 11 +++++++++++ tox.ini | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 2f446327..b2ea2ce9 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -41,6 +41,7 @@ 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 unidecode import unidecode # sentinel @@ -534,8 +535,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if attachment_filename is None: raise TypeError('filename unavailable, required for ' 'sending as attachment') + filename_dict = { + 'filename': unidecode(attachment_filename), + 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} headers.add('Content-Disposition', 'attachment', - filename=attachment_filename) + **filename_dict) if current_app.use_x_sendfile and filename: if file is not None: diff --git a/setup.py b/setup.py index 08995073..2ffe72ed 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ setup( 'Jinja2>=2.4', 'itsdangerous>=0.21', 'click>=2.0', + 'unidecode', ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/test_helpers.py b/tests/test_helpers.py index fd448fb8..362b6cfa 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -560,6 +560,17 @@ class TestSendfile(object): assert options['filename'] == 'index.txt' rv.close() + def test_attachment_with_utf8_filename(self): + 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='Ñandú/pingüino.txt') + value, options = \ + parse_options_header(rv.headers['Content-Disposition']) + assert options == {'filename': 'Nandu/pinguino.txt', 'filename*': "UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"} + rv.close() + def test_static_file(self): app = flask.Flask(__name__) # default cache timeout is 12 hours diff --git a/tox.ini b/tox.ini index 764b4030..3db7b91f 100644 --- a/tox.ini +++ b/tox.ini @@ -27,6 +27,7 @@ deps= devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git simplejson: simplejson + unidecode [testenv:docs] deps = sphinx From 6ef45f30ab0e95d80ee2a29168f098e9037b9e0b Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Fri, 24 Mar 2017 20:05:01 +0100 Subject: [PATCH 333/440] Fix previous commits to work with python 2 and python 3 Also, parse_options_header seems to interpret filename* so we better test the actual value used in the headers (and since it's valid in any order, use a set to compare) --- flask/helpers.py | 2 +- tests/test_helpers.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index b2ea2ce9..e4fb8c43 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -536,7 +536,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') filename_dict = { - 'filename': unidecode(attachment_filename), + 'filename': unidecode(text_type(attachment_filename)), 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} headers.add('Content-Disposition', 'attachment', **filename_dict) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 362b6cfa..f7affb2c 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -565,10 +565,11 @@ class TestSendfile(object): 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='Ñandú/pingüino.txt') - value, options = \ - parse_options_header(rv.headers['Content-Disposition']) - assert options == {'filename': 'Nandu/pinguino.txt', 'filename*': "UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"} + attachment_filename=u'Ñandú/pingüino.txt') + content_disposition = set(rv.headers['Content-Disposition'].split(';')) + assert content_disposition == set(['attachment', + ' filename="Nandu/pinguino.txt"', + " filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"]) rv.close() def test_static_file(self): From bf023e7dc0bae78ff0ab14dc9f10a87e2b56f676 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 30 Mar 2017 17:32:21 +0200 Subject: [PATCH 334/440] Remove unidecode dependency and use unicodedata instead I found a way to remove the unidecode dependency without sacrificing much by using unicodedata.normalize . --- flask/helpers.py | 6 ++++-- setup.py | 1 - tox.ini | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index e4fb8c43..aa8be315 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,6 +14,7 @@ import sys import pkgutil import posixpath import mimetypes +import unicodedata from time import time from zlib import adler32 from threading import RLock @@ -41,7 +42,6 @@ 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 unidecode import unidecode # sentinel @@ -536,7 +536,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') filename_dict = { - 'filename': unidecode(text_type(attachment_filename)), + 'filename': (unicodedata.normalize('NFKD', + text_type(attachment_filename)).encode('ascii', + 'ignore')), 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} headers.add('Content-Disposition', 'attachment', **filename_dict) diff --git a/setup.py b/setup.py index 2ffe72ed..08995073 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,6 @@ setup( 'Jinja2>=2.4', 'itsdangerous>=0.21', 'click>=2.0', - 'unidecode', ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tox.ini b/tox.ini index 3db7b91f..764b4030 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,6 @@ deps= devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git simplejson: simplejson - unidecode [testenv:docs] deps = sphinx From 1d4448abe335741c61b3c8c5f99e1607a13f7e3d Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Fri, 31 Mar 2017 17:07:43 +0100 Subject: [PATCH 335/440] Handle BaseExceptions (#2222) * Handle BaseExceptions * Add test and changes * Make test more idiomatic --- CHANGES | 3 +++ flask/app.py | 3 +++ tests/test_basic.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGES b/CHANGES index 7c50d0c7..6933c0c9 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,9 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. +- Call `ctx.auto_pop` with the exception object instead of `None`, in the + event that a `BaseException` such as `KeyboardInterrupt` is raised in a + request handler. Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index c8540b5f..6617b02b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1991,6 +1991,9 @@ class Flask(_PackageBoundObject): except Exception as e: error = e response = self.handle_exception(e) + except: + error = sys.exc_info()[1] + raise return response(environ, start_response) finally: if self.should_ignore_error(error): diff --git a/tests/test_basic.py b/tests/test_basic.py index 942eb0f6..ffc12dc1 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -791,6 +791,23 @@ def test_error_handling_processing(): assert resp.data == b'internal server error' +def test_baseexception_error_handling(): + app = flask.Flask(__name__) + app.config['LOGGER_HANDLER_POLICY'] = 'never' + + @app.route('/') + def broken_func(): + raise KeyboardInterrupt() + + with app.test_client() as c: + with pytest.raises(KeyboardInterrupt): + c.get('/') + + ctx = flask._request_ctx_stack.top + assert ctx.preserved + assert type(ctx._preserved_exc) is KeyboardInterrupt + + def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From 12c49c75fbd04cfe81808ef300fcb0858d92c7b7 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 23 Mar 2017 14:43:56 +0000 Subject: [PATCH 336/440] Handle BaseExceptions --- flask/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/app.py b/flask/app.py index 942992dc..1404e17e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1983,6 +1983,9 @@ class Flask(_PackageBoundObject): except Exception as e: error = e response = self.handle_exception(e) + except: + error = sys.exc_info()[1] + raise return response(environ, start_response) finally: if self.should_ignore_error(error): From d0e2e7b66c2f0a2192ce4b1b54e118345acf7f6e Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 23 Mar 2017 16:15:00 +0000 Subject: [PATCH 337/440] Add test and changes --- CHANGES | 15 +++++++++++++++ tests/test_basic.py | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/CHANGES b/CHANGES index 03194421..dbb5947a 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,21 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.13 +------------ + +Major release, unreleased + +- Make `app.run()` into a noop if a Flask application is run from the + development server on the command line. This avoids some behavior that + 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. +- Call `ctx.auto_pop` with the exception object instead of `None`, in the + event that a `BaseException` such as `KeyboardInterrupt` is raised in a + request handler. + Version 0.12.1 -------------- diff --git a/tests/test_basic.py b/tests/test_basic.py index be3d5edd..8556268a 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -791,6 +791,26 @@ def test_error_handling_processing(): assert resp.data == b'internal server error' +def test_baseexception_error_handling(): + app = flask.Flask(__name__) + app.config['LOGGER_HANDLER_POLICY'] = 'never' + + @app.route('/') + def broken_func(): + raise KeyboardInterrupt() + + with app.test_client() as c: + try: + c.get('/') + raise AssertionError("KeyboardInterrupt should have been raised") + except KeyboardInterrupt: + pass + + ctx = flask._request_ctx_stack.top + assert ctx.preserved + assert type(ctx._preserved_exc) is KeyboardInterrupt + + def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From 6f7847e3c488fccbfd8c8880683cfe8ae8bdcdc3 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 23 Mar 2017 17:51:45 +0000 Subject: [PATCH 338/440] Make test more idiomatic --- tests/test_basic.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 8556268a..c5ec9f5c 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -800,11 +800,8 @@ def test_baseexception_error_handling(): raise KeyboardInterrupt() with app.test_client() as c: - try: + with pytest.raises(KeyboardInterrupt): c.get('/') - raise AssertionError("KeyboardInterrupt should have been raised") - except KeyboardInterrupt: - pass ctx = flask._request_ctx_stack.top assert ctx.preserved From 80c7db638cfd1f778cc065a26c6ede85fa1ac4e7 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:41:10 +0200 Subject: [PATCH 339/440] Correct changelog --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index dbb5947a..99ae5803 100644 --- a/CHANGES +++ b/CHANGES @@ -14,9 +14,6 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. -- Call `ctx.auto_pop` with the exception object instead of `None`, in the - event that a `BaseException` such as `KeyboardInterrupt` is raised in a - request handler. Version 0.12.1 -------------- @@ -27,6 +24,9 @@ Bugfix release, unreleased within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. +- Call `ctx.auto_pop` with the exception object instead of `None`, in the + event that a `BaseException` such as `KeyboardInterrupt` is raised in a + request handler. Version 0.12 ------------ From f7d6d4d4f60dfbebd80d9bbe9e68ebdd00d89dd6 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:43:34 +0200 Subject: [PATCH 340/440] Prepare for 0.12.1 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 99ae5803..613b8189 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,7 @@ Major release, unreleased Version 0.12.1 -------------- -Bugfix release, unreleased +Bugfix release, released on March 31st 2017 - Prevent `flask run` from showing a NoAppException when an ImportError occurs within the imported application module. From a34d0e6878c8c8a5fab05a69785c443f3c17075d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:43:36 +0200 Subject: [PATCH 341/440] Bump version number to 0.12.1 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 3cef3b43..2fcb3567 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12.1-dev' +__version__ = '0.12.1' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 07a705888cbdf2641f3686f6d87575ef99093c7e Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:43:52 +0200 Subject: [PATCH 342/440] Bump to dev version --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 2fcb3567..59d711b8 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12.1' +__version__ = '0.12.2-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From c935eaceafebaf167f6422fdbd0de3b6bbb96bdf Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:44:14 +0200 Subject: [PATCH 343/440] Revert "Handle BaseExceptions (#2222)" This reverts commit 1d4448abe335741c61b3c8c5f99e1607a13f7e3d. --- CHANGES | 3 --- flask/app.py | 3 --- tests/test_basic.py | 17 ----------------- 3 files changed, 23 deletions(-) diff --git a/CHANGES b/CHANGES index 6933c0c9..7c50d0c7 100644 --- a/CHANGES +++ b/CHANGES @@ -14,9 +14,6 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. -- Call `ctx.auto_pop` with the exception object instead of `None`, in the - event that a `BaseException` such as `KeyboardInterrupt` is raised in a - request handler. Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 6617b02b..c8540b5f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1991,9 +1991,6 @@ class Flask(_PackageBoundObject): except Exception as e: error = e response = self.handle_exception(e) - except: - error = sys.exc_info()[1] - raise return response(environ, start_response) finally: if self.should_ignore_error(error): diff --git a/tests/test_basic.py b/tests/test_basic.py index ffc12dc1..942eb0f6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -791,23 +791,6 @@ def test_error_handling_processing(): assert resp.data == b'internal server error' -def test_baseexception_error_handling(): - app = flask.Flask(__name__) - app.config['LOGGER_HANDLER_POLICY'] = 'never' - - @app.route('/') - def broken_func(): - raise KeyboardInterrupt() - - with app.test_client() as c: - with pytest.raises(KeyboardInterrupt): - c.get('/') - - ctx = flask._request_ctx_stack.top - assert ctx.preserved - assert type(ctx._preserved_exc) is KeyboardInterrupt - - def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From ae1ac2053bcf0c77de32fc7915e36f8ca2f5c961 Mon Sep 17 00:00:00 2001 From: Adam Geitgey Date: Tue, 4 Apr 2017 13:26:40 -0700 Subject: [PATCH 344/440] Correct imports in file upload example (#2230) The example code uses `flash` but doesn't import it. So the code as written doesn't work. This simply adds `flash` to the list of imports in the sample code. --- docs/patterns/fileuploads.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 3a42d325..1c4b0d36 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -21,7 +21,7 @@ specific upload folder and displays a file to the user. Let's look at the bootstrapping code for our application:: import os - from flask import Flask, request, redirect, url_for + from flask import Flask, flash, request, redirect, url_for from werkzeug.utils import secure_filename UPLOAD_FOLDER = '/path/to/the/uploads' From d76d68cd381df4a8c3e2e636c98b5bfa02ab37e4 Mon Sep 17 00:00:00 2001 From: asilversempirical Date: Thu, 6 Apr 2017 11:26:01 -0400 Subject: [PATCH 345/440] Update out of date jsonify documentation https://github.com/pallets/flask/pull/2193 changed the conditions for when jsonify pretty prints, but this comment wasn't updated. --- flask/json.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/flask/json.py b/flask/json.py index 0ba9d717..825dcdf1 100644 --- a/flask/json.py +++ b/flask/json.py @@ -236,11 +236,10 @@ def jsonify(*args, **kwargs): Added support for serializing top-level arrays. This introduces a security risk in ancient browsers. See :ref:`json-security` for details. - This function's response will be pretty printed if it was not requested - with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless - the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false. - Compressed (not pretty) formatting currently means no indents and no - spaces after separators. + This function's response will be pretty printed if the + ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the + Flask app is running in debug mode. Compressed (not pretty) formatting + currently means no indents and no spaces after separators. .. versionadded:: 0.2 """ From ec18fe94775aaa3d54238c6c75b569491803134e Mon Sep 17 00:00:00 2001 From: Grey Li Date: Fri, 7 Apr 2017 22:10:43 +0800 Subject: [PATCH 346/440] Add example for virtualenv integration in cli docs (#2234) --- docs/cli.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/cli.rst b/docs/cli.rst index 2ca0e83e..d0b033f6 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -56,6 +56,18 @@ If you are constantly working with a virtualenv you can also put the bottom of the file. That way every time you activate your virtualenv you automatically also activate the correct application name. +Edit the activate script for the shell you use. For example: + +Unix Bash: ``venv/bin/activate``:: + + FLASK_APP=hello + export FLASK_APP + +Windows CMD.exe: ``venv\Scripts\activate.bat``:: + + set "FLASK_APP=hello" + :END + Debug Flag ---------- From 00d6e339ec789e7b92007297e840a671a5e38a7b Mon Sep 17 00:00:00 2001 From: jab Date: Fri, 7 Apr 2017 10:31:54 -0400 Subject: [PATCH 347/440] Change Flask.__init__ to accept two new keyword arguments, host_matching and static_host. (#1560) 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. Previously, you could only enable host_matching once your app was already instantiated (e.g. app.url_map.host_matching = True), but at that point the constructor would have already added the static route without host matching and an associated host, leaving the static route in a broken state. Fixes #1559. --- AUTHORS | 1 + CHANGES | 5 +++++ flask/app.py | 28 +++++++++++++++++++++------- tests/test_basic.py | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index cc157dc4..33210243 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ Patches and Suggestions - Florent Xicluna - Georg Brandl - Jeff Widman @jeffwidman +- Joshua Bronson @jab - Justin Quick - Kenneth Reitz - Keyan Pishdadian diff --git a/CHANGES b/CHANGES index 9500ee17..440cb260 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,11 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. +- Change Flask.__init__ to accept two new keyword arguments, ``host_matching`` + and ``static_host``. This enables ``host_matching`` to be set properly by the + time the constructor adds the static route, and enables the static route to + be properly associated with the required host. (``#1559``) + Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 6617b02b..87621aee 100644 --- a/flask/app.py +++ b/flask/app.py @@ -123,6 +123,9 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.11 The `root_path` parameter was added. + .. versionadded:: 0.13 + The `host_matching` and `static_host` parameters were added. + :param import_name: the name of the application package :param static_url_path: can be used to specify a different path for the static files on the web. Defaults to the name @@ -130,6 +133,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. + folder in the root path of the application. Defaults + to None. + :param host_matching: sets the app's ``url_map.host_matching`` to the given + 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 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 @@ -337,7 +347,8 @@ class Flask(_PackageBoundObject): session_interface = SecureCookieSessionInterface() def __init__(self, import_name, static_path=None, static_url_path=None, - static_folder='static', template_folder='templates', + static_folder='static', static_host=None, + host_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, root_path=None): _PackageBoundObject.__init__(self, import_name, @@ -525,19 +536,22 @@ class Flask(_PackageBoundObject): #: app.url_map.converters['list'] = ListConverter self.url_map = Map() + self.url_map.host_matching = host_matching + # tracks internally if the application already handled at least one # request. self._got_first_request = False self._before_request_lock = Lock() - # register the static folder for the application. Do that even - # if the folder does not exist. First of all it might be created - # while the server is running (usually happens during development) - # but also because google appengine stores static files somewhere - # else when mapped with the .yml file. + # Add a static route using the provided static_url_path, static_host, + # and static_folder iff there is a configured static_folder. + # Note we do this without checking if static_folder exists. + # For one, it might be created while the server is running (e.g. during + # development). Also, Google App Engine stores static files somewhere if self.has_static_folder: + assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination' self.add_url_rule(self.static_url_path + '/', - endpoint='static', + endpoint='static', host=static_host, view_func=self.send_static_file) #: The click command line context for this application. Commands diff --git a/tests/test_basic.py b/tests/test_basic.py index ffc12dc1..76efd6e2 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1188,6 +1188,25 @@ def test_static_url_path(): assert flask.url_for('static', filename='index.html') == '/foo/index.html' +def test_static_route_with_host_matching(): + app = flask.Flask(__name__, host_matching=True, static_host='example.com') + c = app.test_client() + rv = c.get('http://example.com/static/index.html') + assert rv.status_code == 200 + rv.close() + with app.test_request_context(): + rv = flask.url_for('static', filename='index.html', _external=True) + assert rv == 'http://example.com/static/index.html' + # Providing static_host without host_matching=True should error. + with pytest.raises(Exception): + flask.Flask(__name__, static_host='example.com') + # Providing host_matching=True with static_folder but without static_host should error. + with pytest.raises(Exception): + flask.Flask(__name__, host_matching=True) + # Providing host_matching=True without static_host but with static_folder=None should not error. + flask.Flask(__name__, host_matching=True, static_folder=None) + + def test_none_response(): app = flask.Flask(__name__) app.testing = True From d50a5db5ed07a14733b8cbfc0962b793209014dd Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Fri, 7 Apr 2017 20:34:52 +0200 Subject: [PATCH 348/440] Keep using only filename if it's valid ascii --- flask/helpers.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index aa8be315..19d33420 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,7 +14,7 @@ import sys import pkgutil import posixpath import mimetypes -import unicodedata +from unicodedata import normalize from time import time from zlib import adler32 from threading import RLock @@ -535,13 +535,22 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if attachment_filename is None: raise TypeError('filename unavailable, required for ' 'sending as attachment') - filename_dict = { - 'filename': (unicodedata.normalize('NFKD', - text_type(attachment_filename)).encode('ascii', - 'ignore')), - 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} + normalized = normalize('NFKD', text_type(attachment_filename)) + + try: + normalized.encode('ascii') + except UnicodeEncodeError: + filenames = { + 'filename': normalized.encode('ascii', 'ignore'), + 'filename*': "UTF-8''%s" % url_quote(attachment_filename), + } + else: + filenames = { + 'filename': attachment_filename, + } + headers.add('Content-Disposition', 'attachment', - **filename_dict) + **filenames) if current_app.use_x_sendfile and filename: if file is not None: From c1973016eacfb22fdec837394d8b89c0ca35ad15 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 7 Apr 2017 18:02:31 -0700 Subject: [PATCH 349/440] style cleanup break out header parts in test test for no filename* parameter for ascii header --- flask/helpers.py | 19 ++++++++++++------- tests/test_helpers.py | 25 ++++++++++++++++--------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 19d33420..420467ad 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,10 +14,10 @@ import sys import pkgutil import posixpath import mimetypes -from unicodedata import normalize from time import time from zlib import adler32 from threading import RLock +import unicodedata from werkzeug.routing import BuildError from functools import update_wrapper @@ -478,6 +478,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. versionchanged:: 0.12 The `attachment_filename` is preferred over `filename` for MIME-type detection. + + .. versionchanged:: 0.13 + UTF-8 filenames, as specified in `RFC 2231`_, are supported. + + .. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4 :param filename_or_fp: the filename of the file to send in `latin-1`. This is relative to the :attr:`~Flask.root_path` @@ -535,7 +540,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if attachment_filename is None: raise TypeError('filename unavailable, required for ' 'sending as attachment') - normalized = normalize('NFKD', text_type(attachment_filename)) + + normalized = unicodedata.normalize( + 'NFKD', text_type(attachment_filename) + ) try: normalized.encode('ascii') @@ -545,12 +553,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, 'filename*': "UTF-8''%s" % url_quote(attachment_filename), } else: - filenames = { - 'filename': attachment_filename, - } + filenames = {'filename': attachment_filename} - headers.add('Content-Disposition', 'attachment', - **filenames) + headers.add('Content-Disposition', 'attachment', **filenames) if current_app.use_x_sendfile and filename: if file is not None: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index f7affb2c..1aeff065 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -540,10 +540,11 @@ class TestSendfile(object): value, options = \ parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' + assert options['filename'] == 'index.html' + assert 'filename*' not in options rv.close() with app.test_request_context(): - assert options['filename'] == 'index.html' rv = flask.send_file('static/index.html', as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' @@ -562,15 +563,21 @@ class TestSendfile(object): def test_attachment_with_utf8_filename(self): 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=u'Ñandú/pingüino.txt') - content_disposition = set(rv.headers['Content-Disposition'].split(';')) - assert content_disposition == set(['attachment', - ' filename="Nandu/pinguino.txt"', - " filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"]) - rv.close() + rv = flask.send_file( + 'static/index.html', as_attachment=True, + attachment_filename=u'Ñandú/pingüino.txt' + ) + value, options = parse_options_header( + rv.headers['Content-Disposition'] + ) + rv.close() + + assert value == 'attachment' + assert sorted(options.keys()) == ('filename', 'filename*') + assert options['filename'] == 'Nandu/pinguino.txt' + assert options['filename*'] == 'UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt' def test_static_file(self): app = flask.Flask(__name__) From f790ab7177c9959b560d238f4be9c124d635e70a Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 8 Apr 2017 10:33:06 -0700 Subject: [PATCH 350/440] need to test against raw header parsing prefers the last value parsed for the option --- tests/test_helpers.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 1aeff065..d93b443e 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -541,7 +541,7 @@ class TestSendfile(object): parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' assert options['filename'] == 'index.html' - assert 'filename*' not in options + assert 'filename*' not in rv.headers['Content-Disposition'] rv.close() with app.test_request_context(): @@ -565,20 +565,15 @@ class TestSendfile(object): app = flask.Flask(__name__) with app.test_request_context(): - rv = flask.send_file( - 'static/index.html', as_attachment=True, - attachment_filename=u'Ñandú/pingüino.txt' - ) - value, options = parse_options_header( - rv.headers['Content-Disposition'] - ) + rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=u'Ñandú/pingüino.txt') + content_disposition = set(rv.headers['Content-Disposition'].split('; ')) + assert content_disposition == set(( + 'attachment', + 'filename="Nandu/pinguino.txt"', + "filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt" + )) rv.close() - assert value == 'attachment' - assert sorted(options.keys()) == ('filename', 'filename*') - assert options['filename'] == 'Nandu/pinguino.txt' - assert options['filename*'] == 'UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt' - def test_static_file(self): app = flask.Flask(__name__) # default cache timeout is 12 hours From aafb80c527eb5d6d361d7ba08258376d13679820 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 8 Apr 2017 11:08:08 -0700 Subject: [PATCH 351/440] add changelog for #2223 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 440cb260..1f687409 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,9 @@ Major release, unreleased 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`_) +.. _#2223: https://github.com/pallets/flask/pull/2223 Version 0.12.1 -------------- From e13eaeeaf2a3502815bcf04a86227dae87ad9128 Mon Sep 17 00:00:00 2001 From: ka7 Date: Tue, 11 Apr 2017 21:44:32 +0200 Subject: [PATCH 352/440] Fix typo in docs (#2237) --- docs/patterns/packages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 1bb84f8c..cc149839 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -65,7 +65,7 @@ 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. Similiarly you can turn on "debug +path to your application directory. Similarly you can turn on "debug mode" with this environment variable:: export FLASK_DEBUG=true From 09b49104f39f111b58d60a819551cca4bde305cc Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 12 Apr 2017 09:18:07 -0700 Subject: [PATCH 353/440] filename can be latin-1, not just ascii only normalize basic name when utf-8 header is needed ref #2223 --- flask/helpers.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 420467ad..bfdcf224 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -484,7 +484,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4 - :param filename_or_fp: the filename of the file to send in `latin-1`. + :param filename_or_fp: the filename of the file to send. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. Alternatively a file object might be provided in @@ -541,15 +541,12 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') - normalized = unicodedata.normalize( - 'NFKD', text_type(attachment_filename) - ) - try: - normalized.encode('ascii') + attachment_filename = attachment_filename.encode('latin-1') except UnicodeEncodeError: filenames = { - 'filename': normalized.encode('ascii', 'ignore'), + 'filename': unicodedata.normalize( + 'NFKD', attachment_filename).encode('latin-1', 'ignore'), 'filename*': "UTF-8''%s" % url_quote(attachment_filename), } else: From bf6910a639fffc6896c5aefe362edaf6f6b704fe Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Apr 2017 14:55:56 -0700 Subject: [PATCH 354/440] get mtime in utc --- tests/test_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index d93b443e..d3906860 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -517,7 +517,7 @@ class TestSendfile(object): assert rv.status_code == 416 rv.close() - last_modified = datetime.datetime.fromtimestamp(os.path.getmtime( + last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime( os.path.join(app.root_path, 'static/index.html'))).replace( microsecond=0) From 1caa9de6286c5575accb05b942531080142242d3 Mon Sep 17 00:00:00 2001 From: accraze Date: Mon, 19 Dec 2016 08:03:37 -0800 Subject: [PATCH 355/440] Added missing testing config fixes #1302 --- docs/testing.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index 0737936e..edaa597c 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -98,8 +98,10 @@ test method to our class, like this:: def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + flaskr.app.config['TESTING'] = True self.app = flaskr.app.test_client() - flaskr.init_db() + with flaskr.app.app_context(): + flaskr.init_db() def tearDown(self): os.close(self.db_fd) From 03857cc48a1aff7af4c2bbc18a5740d1dc903c0b Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Apr 2017 16:32:44 -0700 Subject: [PATCH 356/440] use app.testing property instead of config --- docs/testing.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index edaa597c..6fd7b504 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -41,7 +41,7 @@ In order to test the application, we add a second module def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + flaskr.app.testing = True self.app = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() @@ -98,7 +98,7 @@ test method to our class, like this:: def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + flaskr.app.testing = True self.app = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() @@ -210,7 +210,7 @@ temporarily. With this you can access the :class:`~flask.request`, functions. Here is a full example that demonstrates this approach:: import flask - + app = flask.Flask(__name__) with app.test_request_context('/?name=Peter'): From cfd3e50ab6447a69019fc008c260f8c078c4ac7d Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Apr 2017 16:32:44 -0700 Subject: [PATCH 357/440] use app.testing property instead of config --- docs/testing.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 0737936e..6fd7b504 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -41,7 +41,7 @@ In order to test the application, we add a second module def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + flaskr.app.testing = True self.app = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() @@ -98,8 +98,10 @@ test method to our class, like this:: def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + flaskr.app.testing = True self.app = flaskr.app.test_client() - flaskr.init_db() + with flaskr.app.app_context(): + flaskr.init_db() def tearDown(self): os.close(self.db_fd) @@ -208,7 +210,7 @@ temporarily. With this you can access the :class:`~flask.request`, functions. Here is a full example that demonstrates this approach:: import flask - + app = flask.Flask(__name__) with app.test_request_context('/?name=Peter'): From 4ff84d537aa386fde36182ed797a79e3b582be75 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Apr 2017 14:55:56 -0700 Subject: [PATCH 358/440] get mtime in utc --- tests/test_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3e2ea8cd..b1241ce9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -515,7 +515,7 @@ class TestSendfile(object): assert rv.status_code == 416 rv.close() - last_modified = datetime.datetime.fromtimestamp(os.path.getmtime( + last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime( os.path.join(app.root_path, 'static/index.html'))).replace( microsecond=0) From 7481844c98e4536ae01764aa08eec681c493ef2e Mon Sep 17 00:00:00 2001 From: Sobolev Nikita Date: Wed, 19 Apr 2017 08:46:33 +0300 Subject: [PATCH 359/440] Fix typo in app.py (#2248) --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 87621aee..c26387f0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -544,7 +544,7 @@ class Flask(_PackageBoundObject): self._before_request_lock = Lock() # Add a static route using the provided static_url_path, static_host, - # and static_folder iff there is a configured static_folder. + # and static_folder if there is a configured static_folder. # Note we do this without checking if static_folder exists. # For one, it might be created while the server is running (e.g. during # development). Also, Google App Engine stores static files somewhere From 19fbe3a18f36b02f22f8f545e015f60343c844ac Mon Sep 17 00:00:00 2001 From: rocambolesque Date: Fri, 9 Sep 2016 12:11:18 +0200 Subject: [PATCH 360/440] Add scheme to url_build error handler parameters --- CHANGES | 3 +++ flask/helpers.py | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 1f687409..d8f2577a 100644 --- a/CHANGES +++ b/CHANGES @@ -19,7 +19,10 @@ Major release, unreleased 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`_) +.. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 Version 0.12.1 diff --git a/flask/helpers.py b/flask/helpers.py index bfdcf224..828f5840 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -331,6 +331,7 @@ def url_for(endpoint, **values): values['_external'] = external values['_anchor'] = anchor values['_method'] = method + values['_scheme'] = scheme return appctx.app.handle_url_build_error(error, endpoint, values) if anchor is not None: From e50767cfca081681f527c54d3f6652d11e95e758 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 Apr 2017 08:52:37 -0700 Subject: [PATCH 361/440] add test for build error special values --- tests/test_basic.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index 76efd6e2..abce3ad4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1131,6 +1131,23 @@ def test_build_error_handler_reraise(): pytest.raises(BuildError, flask.url_for, 'not.existing') +def test_url_for_passes_special_values_to_build_error_handler(): + app = flask.Flask(__name__) + + @app.url_build_error_handlers.append + def handler(error, endpoint, values): + assert values == { + '_external': False, + '_anchor': None, + '_method': None, + '_scheme': None, + } + return 'handled' + + with app.test_request_context(): + flask.url_for('/') + + def test_custom_converters(): from werkzeug.routing import BaseConverter From 97e2cd0a5a5f2b663d7a82edbaede2fe2cfb0679 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 21 Apr 2017 07:16:09 -0700 Subject: [PATCH 362/440] update changelog move test next to existing test, rename reword / reflow param doc --- CHANGES | 7 ++-- flask/app.py | 11 +++--- tests/test_basic.py | 91 ++++++++++++++++++++++++--------------------- 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/CHANGES b/CHANGES index 34f7f13e..dba0112e 100644 --- a/CHANGES +++ b/CHANGES @@ -21,7 +21,11 @@ Major release, unreleased - ``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`_). +.. _#1489: https://github.com/pallets/flask/pull/1489 .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 @@ -146,9 +150,6 @@ Released on May 29th 2016, codename Absinthe. - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). -- Added support for `provide_automatic_options` in :meth:`add_url_rule` to - turn off automatic OPTIONS when the `view_func` argument is not a class - (pull request ``#1489``). - ``flask.ext`` is now deprecated (pull request ``#1484``). - ``send_from_directory`` now raises BadRequest if the filename is invalid on the server OS (pull request ``#1763``). diff --git a/flask/app.py b/flask/app.py index 32f5086c..a054d23c 100644 --- a/flask/app.py +++ b/flask/app.py @@ -980,8 +980,7 @@ 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. @@ -1021,10 +1020,10 @@ class Flask(_PackageBoundObject): endpoint :param view_func: the function to call when serving a request to the provided endpoint - :param provide_automatic_options: controls whether ``OPTIONS`` should - be provided automatically. If this - is not set, will check attributes on - the view or list of methods. + :param provide_automatic_options: controls whether the ``OPTIONS`` + method should be added automatically. This can also be controlled + by setting the ``view_func.provide_automatic_options = False`` + before adding the rule. :param options: the options to be forwarded to the underlying :class:`~werkzeug.routing.Rule` object. A change to Werkzeug is handling of method options. methods diff --git a/tests/test_basic.py b/tests/test_basic.py index 3fe69588..677b4be8 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -50,7 +50,7 @@ def test_options_on_multiple_rules(): assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] -def test_options_handling_disabled(): +def test_provide_automatic_options_attr(): app = flask.Flask(__name__) def index(): @@ -70,6 +70,54 @@ def test_options_handling_disabled(): assert sorted(rv.allow) == ['OPTIONS'] +def test_provide_automatic_options_kwarg(): + app = flask.Flask(__name__) + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule('/', view_func=index, provide_automatic_options=False) + app.add_url_rule( + '/more', view_func=more, methods=['GET', 'POST'], + provide_automatic_options=False + ) + + c = app.test_client() + assert c.get('/').data == b'GET' + + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD'] + + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/') + else: + rv = c.open('/', method='OPTIONS') + + assert rv.status_code == 405 + + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == b'POST' + assert c.get('/more').data == b'GET' + + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] + + if hasattr(c, 'options'): + rv = c.options('/more') + else: + rv = c.open('/more', method='OPTIONS') + + assert rv.status_code == 405 + + def test_request_dispatching(): app = flask.Flask(__name__) @@ -1751,44 +1799,3 @@ def test_run_from_config(monkeypatch, host, port, expect_host, expect_port): app = flask.Flask(__name__) app.config['SERVER_NAME'] = 'pocoo.org:8080' app.run(host, port) - - -def test_disable_automatic_options(): - # Issue 1488: Add support for a kwarg to add_url_rule to disable the auto OPTIONS response - app = flask.Flask(__name__) - - def index(): - return flask.request.method - - def more(): - return flask.request.method - - app.add_url_rule('/', 'index', index, provide_automatic_options=False) - app.add_url_rule('/more', 'more', more, methods=['GET', 'POST'], provide_automatic_options=False) - - c = app.test_client() - assert c.get('/').data == b'GET' - rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD'] - # Older versions of Werkzeug.test.Client don't have an options method - if hasattr(c, 'options'): - rv = c.options('/') - else: - rv = c.open('/', method='OPTIONS') - assert rv.status_code == 405 - - rv = c.head('/') - assert rv.status_code == 200 - assert not rv.data # head truncates - assert c.post('/more').data == b'POST' - assert c.get('/more').data == b'GET' - rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] - # Older versions of Werkzeug.test.Client don't have an options method - if hasattr(c, 'options'): - rv = c.options('/more') - else: - rv = c.open('/more', method='OPTIONS') - assert rv.status_code == 405 From 648344d4e8878e4aafcc5413f984b21b86173246 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 21 Apr 2017 10:32:00 -0700 Subject: [PATCH 363/440] use mro to collect methods ignore methods attr unless explicitly set add changelog --- CHANGES | 2 ++ flask/views.py | 44 ++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index dba0112e..4fd46d71 100644 --- a/CHANGES +++ b/CHANGES @@ -24,8 +24,10 @@ Major release, unreleased - 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`_) .. _#1489: https://github.com/pallets/flask/pull/1489 +.. _#1936: https://github.com/pallets/flask/pull/1936 .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 diff --git a/flask/views.py b/flask/views.py index 757c2a4d..848ccb0b 100644 --- a/flask/views.py +++ b/flask/views.py @@ -102,38 +102,35 @@ class View(object): return view -def get_methods(cls): - return getattr(cls, 'methods', []) or [] - - class MethodViewType(type): + """Metaclass for :class:`MethodView` that determines what methods the view + defines. + """ + + def __init__(cls, name, bases, d): + super(MethodViewType, cls).__init__(name, bases, d) - def __new__(cls, name, bases, d): - rv = type.__new__(cls, name, bases, d) if 'methods' not in d: - methods = set(m for b in bases for m in get_methods(b)) - for key in d: - if key in http_method_funcs: + methods = set() + + for key in http_method_funcs: + if hasattr(cls, key): methods.add(key.upper()) - # If we have no method at all in there we don't want to - # add a method list. (This is for instance the case for - # the base class or another subclass of a base method view - # that does not introduce new methods). + + # If we have no method at all in there we don't want to add a + # method list. This is for instance the case for the base class + # or another subclass of a base method view that does not introduce + # new methods. if methods: - rv.methods = sorted(methods) - return rv + cls.methods = methods class MethodView(with_metaclass(MethodViewType, View)): - """Like a regular class-based view but that dispatches requests to - particular methods. For instance if you implement a method called - :meth:`get` it means it will respond to ``'GET'`` requests and - the :meth:`dispatch_request` implementation will automatically - forward your request to that. Also :attr:`options` is set for you - automatically:: + """A class-based view that dispatches request methods to the corresponding + class methods. For example, if you implement a ``get`` method, it will be + used to handle ``GET`` requests. :: class CounterAPI(MethodView): - def get(self): return session.get('counter', 0) @@ -143,11 +140,14 @@ class MethodView(with_metaclass(MethodViewType, View)): app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter')) """ + def dispatch_request(self, *args, **kwargs): meth = getattr(self, request.method.lower(), None) + # If the request method is HEAD and we don't have a handler for it # retry with GET. if meth is None and request.method == 'HEAD': meth = getattr(self, 'get', None) + assert meth is not None, 'Unimplemented method %r' % request.method return meth(*args, **kwargs) From 13754b6d117eb6847cca45475412fdd05025f1b8 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 22 Apr 2017 13:39:54 -0700 Subject: [PATCH 364/440] ensure error while opening session pops context errors will be handled by the app error handlers closes #1538, closes #1528 --- CHANGES | 3 +++ flask/app.py | 2 +- tests/test_reqctx.py | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4fd46d71..d7f15d66 100644 --- a/CHANGES +++ b/CHANGES @@ -25,11 +25,14 @@ Major release, unreleased 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`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1936: https://github.com/pallets/flask/pull/1936 .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 +.. _#2254: https://github.com/pallets/flask/pull/2254 Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index a054d23c..1943cfcf 100644 --- a/flask/app.py +++ b/flask/app.py @@ -2002,10 +2002,10 @@ class Flask(_PackageBoundObject): exception context to start the response """ ctx = self.request_context(environ) - ctx.push() error = None try: try: + ctx.push() response = self.full_dispatch_request() except Exception as e: error = e diff --git a/tests/test_reqctx.py b/tests/test_reqctx.py index 4b2b1f87..48823fb2 100644 --- a/tests/test_reqctx.py +++ b/tests/test_reqctx.py @@ -12,6 +12,7 @@ import pytest import flask +from flask.sessions import SessionInterface try: from greenlet import greenlet @@ -193,3 +194,27 @@ def test_greenlet_context_copying_api(): result = greenlets[0].run() assert result == 42 + + +def test_session_error_pops_context(): + class SessionError(Exception): + pass + + class FailingSessionInterface(SessionInterface): + def open_session(self, app, request): + raise SessionError() + + class CustomFlask(flask.Flask): + session_interface = FailingSessionInterface() + + app = CustomFlask(__name__) + + @app.route('/') + def index(): + # shouldn't get here + assert False + + response = app.test_client().get('/') + assert response.status_code == 500 + assert not flask.request + assert not flask.current_app From 46f83665ef74880bbc4a1c1f17ccededa7aaa939 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 24 Apr 2017 10:09:50 -0700 Subject: [PATCH 365/440] clean up blueprint json support add changelog for #1898 --- CHANGES | 3 +++ flask/blueprints.py | 4 ++-- flask/json.py | 24 ++++++++++++++---------- tests/test_helpers.py | 16 +++++++++++----- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index d7f15d66..ae592ba8 100644 --- a/CHANGES +++ b/CHANGES @@ -27,8 +27,11 @@ Major release, unreleased - ``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`_) .. _#1489: https://github.com/pallets/flask/pull/1489 +.. _#1898: https://github.com/pallets/flask/pull/1898 .. _#1936: https://github.com/pallets/flask/pull/1936 .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 diff --git a/flask/blueprints.py b/flask/blueprints.py index 62675204..57d77512 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -90,10 +90,10 @@ class Blueprint(_PackageBoundObject): _got_registered_once = False #: Blueprint local JSON decoder class to use. - # Set to None to use the :class:`~flask.app.Flask.json_encoder`. + #: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`. json_encoder = None #: Blueprint local JSON decoder class to use. - # Set to None to use the :class:`~flask.app.Flask.json_decoder`. + #: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`. json_decoder = None def __init__(self, name, import_name, static_folder=None, diff --git a/flask/json.py b/flask/json.py index 77b5fce1..bf8a8843 100644 --- a/flask/json.py +++ b/flask/json.py @@ -92,13 +92,16 @@ class JSONDecoder(_json.JSONDecoder): def _dump_arg_defaults(kwargs): """Inject default arguments for dump functions.""" if current_app: - bp = current_app.blueprints.get(request.blueprint, - None) if has_request_context() else None - kwargs.setdefault('cls', - bp.json_encoder if bp and bp.json_encoder - else current_app.json_encoder) + bp = current_app.blueprints.get(request.blueprint) if request else None + kwargs.setdefault( + 'cls', + bp.json_encoder if bp and bp.json_encoder + else current_app.json_encoder + ) + if not current_app.config['JSON_AS_ASCII']: kwargs.setdefault('ensure_ascii', False) + kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS']) else: kwargs.setdefault('sort_keys', True) @@ -108,11 +111,12 @@ def _dump_arg_defaults(kwargs): def _load_arg_defaults(kwargs): """Inject default arguments for load functions.""" if current_app: - bp = current_app.blueprints.get(request.blueprint, - None) if has_request_context() else None - kwargs.setdefault('cls', - bp.json_decoder if bp and bp.json_decoder - else current_app.json_decoder) + bp = current_app.blueprints.get(request.blueprint) if request else None + kwargs.setdefault( + 'cls', + bp.json_decoder if bp and bp.json_decoder + else current_app.json_decoder + ) else: kwargs.setdefault('cls', JSONDecoder) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index c811e1b7..325713c0 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -271,30 +271,36 @@ class TestJSON(object): class X(object): def __init__(self, val): self.val = val + class MyEncoder(flask.json.JSONEncoder): def default(self, o): if isinstance(o, X): return '<%d>' % o.val + return flask.json.JSONEncoder.default(self, o) + class MyDecoder(flask.json.JSONDecoder): def __init__(self, *args, **kwargs): kwargs.setdefault('object_hook', self.object_hook) flask.json.JSONDecoder.__init__(self, *args, **kwargs) + def object_hook(self, obj): if len(obj) == 1 and '_foo' in obj: return X(obj['_foo']) + return obj - blue = flask.Blueprint('blue', __name__) - blue.json_encoder = MyEncoder - blue.json_decoder = MyDecoder - @blue.route('/bp', methods=['POST']) + bp = flask.Blueprint('bp', __name__) + bp.json_encoder = MyEncoder + bp.json_decoder = MyDecoder + + @bp.route('/bp', methods=['POST']) def index(): return flask.json.dumps(flask.request.get_json()['x']) app = flask.Flask(__name__) app.testing = True - app.register_blueprint(blue) + app.register_blueprint(bp) c = app.test_client() rv = c.post('/bp', data=flask.json.dumps({ From 697f7b9365304c45216e6c8dd307d931ea1506d0 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 24 Apr 2017 14:11:49 -0700 Subject: [PATCH 366/440] refactor make_response to be easier to follow * be explicit about how tuples are unpacked * allow bytes for status value * allow Headers for headers value * use TypeError instead of ValueError * errors are more descriptive * document that view must not return None * update documentation about return values * test more response types * test error messages closes #1676 --- CHANGES | 4 ++ flask/app.py | 170 ++++++++++++++++++++++++++++---------------- tests/test_basic.py | 143 +++++++++++++++++++++++++------------ 3 files changed, 207 insertions(+), 110 deletions(-) diff --git a/CHANGES b/CHANGES index ae592ba8..11ac6430 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,9 @@ Major release, unreleased 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`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1898: https://github.com/pallets/flask/pull/1898 @@ -36,6 +39,7 @@ Major release, unreleased .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 .. _#2254: https://github.com/pallets/flask/pull/2254 +.. _#2256: https://github.com/pallets/flask/pull/2256 Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 1943cfcf..a938344b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -10,30 +10,30 @@ """ import os import sys -from threading import Lock from datetime import timedelta -from itertools import chain from functools import update_wrapper +from itertools import chain +from threading import Lock -from werkzeug.datastructures import ImmutableDict -from werkzeug.routing import Map, Rule, RequestRedirect, BuildError -from werkzeug.exceptions import HTTPException, InternalServerError, \ - MethodNotAllowed, BadRequest, default_exceptions - -from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ - locked_cached_property, _endpoint_from_view_func, find_package, \ - get_debug_flag -from . import json, cli -from .wrappers import Request, Response -from .config import ConfigAttribute, Config -from .ctx import RequestContext, AppContext, _AppCtxGlobals -from .globals import _request_ctx_stack, request, session, g +from werkzeug.datastructures import ImmutableDict, Headers +from werkzeug.exceptions import BadRequest, HTTPException, \ + InternalServerError, MethodNotAllowed, default_exceptions +from werkzeug.routing import BuildError, Map, RequestRedirect, Rule + +from . import cli, json +from ._compat import integer_types, reraise, string_types, text_type +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, \ + get_flashed_messages, locked_cached_property, url_for from .sessions import SecureCookieSessionInterface +from .signals import appcontext_tearing_down, got_request_exception, \ + request_finished, request_started, request_tearing_down from .templating import DispatchingJinjaLoader, Environment, \ - _default_template_ctx_processor -from .signals import request_started, request_finished, got_request_exception, \ - request_tearing_down, appcontext_tearing_down -from ._compat import reraise, string_types, text_type, integer_types + _default_template_ctx_processor +from .wrappers import Request, Response # a lock used for logger initialization _logger_lock = Lock() @@ -1715,62 +1715,106 @@ class Flask(_PackageBoundObject): return False def make_response(self, rv): - """Converts the return value from a view function to a real - response object that is an instance of :attr:`response_class`. - - The following types are allowed for `rv`: - - .. tabularcolumns:: |p{3.5cm}|p{9.5cm}| - - ======================= =========================================== - :attr:`response_class` the object is returned unchanged - :class:`str` a response object is created with the - string as body - :class:`unicode` a response object is created with the - string encoded to utf-8 as body - a WSGI function the function is called as WSGI application - and buffered as response object - :class:`tuple` A tuple in the form ``(response, status, - headers)`` or ``(response, headers)`` - where `response` is any of the - types defined here, `status` is a string - or an integer and `headers` is a list or - a dictionary with header values. - ======================= =========================================== - - :param rv: the return value from the view function + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` (``unicode`` in Python 2) + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` (``str`` in Python 2) + A response object is created with the bytes as the body. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. .. versionchanged:: 0.9 Previously a tuple was interpreted as the arguments for the response object. """ - status_or_headers = headers = None - if isinstance(rv, tuple): - rv, status_or_headers, headers = rv + (None,) * (3 - len(rv)) - if rv is None: - raise ValueError('View function did not return a response') + status = headers = None + + # unpack tuple returns + if isinstance(rv, (tuple, list)): + len_rv = len(rv) - if isinstance(status_or_headers, (dict, list)): - headers, status_or_headers = status_or_headers, None + # a 3-tuple is unpacked directly + if len_rv == 3: + rv, status, headers = rv + # decide if a 2-tuple has status or headers + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv + else: + rv, status = rv + # other sized tuples are not allowed + else: + raise TypeError( + 'The view function did not return a valid response tuple.' + ' The tuple must have the form (body, status, headers),' + ' (body, status), or (body, headers).' + ) + # the body must not be None + if rv is None: + raise TypeError( + 'The view function did not return a valid response. The' + ' function either returned None or ended without a return' + ' statement.' + ) + + # make sure the body is an instance of the response class if not isinstance(rv, self.response_class): - # When we create a response object directly, we let the constructor - # set the headers and status. We do this because there can be - # some extra logic involved when creating these objects with - # specific values (like default content type selection). if isinstance(rv, (text_type, bytes, bytearray)): - rv = self.response_class(rv, headers=headers, - status=status_or_headers) - headers = status_or_headers = None + # let the response class set the status and headers instead of + # waiting to do it manually, so that the class can handle any + # special logic + rv = self.response_class(rv, status=status, headers=headers) + status = headers = None else: - rv = self.response_class.force_type(rv, request.environ) - - if status_or_headers is not None: - if isinstance(status_or_headers, string_types): - rv.status = status_or_headers + # evaluate a WSGI callable, or coerce a different response + # class to the correct type + try: + rv = self.response_class.force_type(rv, request.environ) + except TypeError as e: + new_error = TypeError( + '{e}\nThe view function did not return a valid' + ' response. The return type must be a string, tuple,' + ' Response instance, or WSGI callable, but it was a' + ' {rv.__class__.__name__}.'.format(e=e, rv=rv) + ) + reraise(TypeError, new_error, sys.exc_info()[2]) + + # prefer the status if it was provided + if status is not None: + if isinstance(status, (text_type, bytes, bytearray)): + rv.status = status else: - rv.status_code = status_or_headers + rv.status_code = status + + # extend existing headers with provided headers if headers: rv.headers.extend(headers) diff --git a/tests/test_basic.py b/tests/test_basic.py index 677b4be8..163b83cf 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -975,64 +975,129 @@ def test_enctype_debug_helper(): assert 'This was submitted: "index.txt"' in str(e.value) -def test_response_creation(): +def test_response_types(): app = flask.Flask(__name__) + app.testing = True - @app.route('/unicode') - def from_unicode(): + @app.route('/text') + def from_text(): return u'Hällo Wörld' - @app.route('/string') - def from_string(): + @app.route('/bytes') + def from_bytes(): return u'Hällo Wörld'.encode('utf-8') - @app.route('/args') - def from_tuple(): + @app.route('/full_tuple') + def from_full_tuple(): return 'Meh', 400, { 'X-Foo': 'Testing', 'Content-Type': 'text/plain; charset=utf-8' } - @app.route('/two_args') - def from_two_args_tuple(): + @app.route('/text_headers') + def from_text_headers(): return 'Hello', { 'X-Foo': 'Test', 'Content-Type': 'text/plain; charset=utf-8' } - @app.route('/args_status') - def from_status_tuple(): + @app.route('/text_status') + def from_text_status(): return 'Hi, status!', 400 - @app.route('/args_header') - def from_response_instance_status_tuple(): - return flask.Response('Hello world', 404), { + @app.route('/response_headers') + def from_response_headers(): + return flask.Response('Hello world', 404, {'X-Foo': 'Baz'}), { "X-Foo": "Bar", "X-Bar": "Foo" } + @app.route('/response_status') + def from_response_status(): + return app.response_class('Hello world', 400), 500 + + @app.route('/wsgi') + def from_wsgi(): + return NotFound() + c = app.test_client() - assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') - assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') - rv = c.get('/args') + + assert c.get('/text').data == u'Hällo Wörld'.encode('utf-8') + assert c.get('/bytes').data == u'Hällo Wörld'.encode('utf-8') + + rv = c.get('/full_tuple') assert rv.data == b'Meh' assert rv.headers['X-Foo'] == 'Testing' assert rv.status_code == 400 assert rv.mimetype == 'text/plain' - rv2 = c.get('/two_args') - assert rv2.data == b'Hello' - assert rv2.headers['X-Foo'] == 'Test' - assert rv2.status_code == 200 - assert rv2.mimetype == 'text/plain' - rv3 = c.get('/args_status') - assert rv3.data == b'Hi, status!' - assert rv3.status_code == 400 - assert rv3.mimetype == 'text/html' - rv4 = c.get('/args_header') - assert rv4.data == b'Hello world' - assert rv4.headers['X-Foo'] == 'Bar' - assert rv4.headers['X-Bar'] == 'Foo' - assert rv4.status_code == 404 + + rv = c.get('/text_headers') + assert rv.data == b'Hello' + assert rv.headers['X-Foo'] == 'Test' + assert rv.status_code == 200 + assert rv.mimetype == 'text/plain' + + rv = c.get('/text_status') + assert rv.data == b'Hi, status!' + assert rv.status_code == 400 + assert rv.mimetype == 'text/html' + + rv = c.get('/response_headers') + assert rv.data == b'Hello world' + assert rv.headers.getlist('X-Foo') == ['Baz', 'Bar'] + assert rv.headers['X-Bar'] == 'Foo' + assert rv.status_code == 404 + + rv = c.get('/response_status') + assert rv.data == b'Hello world' + assert rv.status_code == 500 + + rv = c.get('/wsgi') + assert b'Not Found' in rv.data + assert rv.status_code == 404 + + +def test_response_type_errors(): + app = flask.Flask(__name__) + app.testing = True + + @app.route('/none') + def from_none(): + pass + + @app.route('/small_tuple') + def from_small_tuple(): + return 'Hello', + + @app.route('/large_tuple') + def from_large_tuple(): + return 'Hello', 234, {'X-Foo': 'Bar'}, '???' + + @app.route('/bad_type') + def from_bad_type(): + return True + + @app.route('/bad_wsgi') + def from_bad_wsgi(): + return lambda: None + + c = app.test_client() + + with pytest.raises(TypeError) as e: + c.get('/none') + assert 'returned None' in str(e) + + with pytest.raises(TypeError) as e: + c.get('/small_tuple') + assert 'tuple must have the form' in str(e) + + pytest.raises(TypeError, c.get, '/large_tuple') + + with pytest.raises(TypeError) as e: + c.get('/bad_type') + assert 'it was a bool' in str(e) + + pytest.raises(TypeError, c.get, '/bad_wsgi') def test_make_response(): @@ -1272,22 +1337,6 @@ def test_static_route_with_host_matching(): flask.Flask(__name__, host_matching=True, static_folder=None) -def test_none_response(): - app = flask.Flask(__name__) - app.testing = True - - @app.route('/') - def test(): - return None - try: - app.test_client().get('/') - except ValueError as e: - assert str(e) == 'View function did not return a response' - pass - else: - assert "Expected ValueError" - - def test_request_locals(): assert repr(flask.g) == '' assert not flask.g From 501f0431259a30569a5e62bcce68d102fc3ef993 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 25 Apr 2017 12:03:08 -0700 Subject: [PATCH 367/440] clean up preprocess_request docs [ci skip] --- flask/app.py | 70 ++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/flask/app.py b/flask/app.py index 84f55d2c..703cfef2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -415,17 +415,16 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.9 self.url_build_error_handlers = [] - #: A dictionary with lists of functions that should be called at the - #: beginning of the request. The key of the dictionary is the name of - #: the blueprint this function is active for, ``None`` for all requests. - #: This can for example be used to open database connections or - #: getting hold of the currently logged in user. To register a - #: function here, use the :meth:`before_request` decorator. + #: A dictionary with lists of functions that will be called at the + #: beginning of each request. The key of the dictionary is the name of + #: the blueprint this function is active for, or ``None`` for all + #: requests. To register a function, use the :meth:`before_request` + #: decorator. self.before_request_funcs = {} - #: A lists of functions that should be called at the beginning of the - #: first request to this instance. To register a function here, use - #: the :meth:`before_first_request` decorator. + #: A list of functions that will be called at the beginning of the + #: first request to this instance. To register a function, use the + #: :meth:`before_first_request` decorator. #: #: .. versionadded:: 0.8 self.before_first_request_funcs = [] @@ -457,12 +456,11 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.9 self.teardown_appcontext_funcs = [] - #: A dictionary with lists of functions that can be used as URL - #: value processor functions. Whenever a URL is built these functions - #: are called to modify the dictionary of values in place. The key - #: ``None`` here is used for application wide - #: callbacks, otherwise the key is the name of the blueprint. - #: Each of these functions has the chance to modify the dictionary + #: A dictionary with lists of functions that are called before the + #: :attr:`before_request_funcs` functions. The key of the dictionary is + #: the name of the blueprint this function is active for, or ``None`` + #: for all requests. To register a function, use + #: :meth:`url_value_preprocessor`. #: #: .. versionadded:: 0.7 self.url_value_preprocessors = {} @@ -1314,11 +1312,13 @@ class Flask(_PackageBoundObject): @setupmethod def before_request(self, f): """Registers a function to run before each request. + + For example, this can be used to open a database connection, or to load + the logged in user from the session. - The function will be called without any arguments. - If the function returns a non-None value, it's handled as - if it was the return value from the view and further - request handling is stopped. + The function will be called without any arguments. If it returns a + non-None value, the value is handled as if it was the return value from + the view, and further request handling is stopped. """ self.before_request_funcs.setdefault(None, []).append(f) return f @@ -1437,9 +1437,17 @@ class Flask(_PackageBoundObject): @setupmethod def url_value_preprocessor(self, f): - """Registers a function as URL value preprocessor for all view - functions of the application. It's called before the view functions - are called and can modify the url values provided. + """Register a URL value preprocessor function for all view + functions in the application. These functions will be called before the + :meth:`before_request` functions. + + The function can modify the values captured from the matched url before + they are passed to the view. For example, this can be used to pop a + common language code value and place it in ``g`` rather than pass it to + every view. + + The function is passed the endpoint name and values dict. The return + value is ignored. """ self.url_value_preprocessors.setdefault(None, []).append(f) return f @@ -1877,16 +1885,14 @@ class Flask(_PackageBoundObject): raise error def preprocess_request(self): - """Called before the request dispatching. - - Triggers two set of hook functions that should be invoked prior to request dispatching: - :attr:`url_value_preprocessors` and :attr:`before_request_funcs` - (the latter are functions decorated with :meth:`before_request` decorator). - In both cases, the method triggers only the functions that are either global - or registered to the current blueprint. - - If any function in :attr:`before_request_funcs` returns a value, it's handled as if it was - the return value from the view function, and further request handling is stopped. + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler returns a non-None value, the + value is handled as if it was the return value from the view, and + further request handling is stopped. """ bp = _request_ctx_stack.top.request.blueprint From 7ad79583b9558cc7806d56c534f9527e500734e9 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 25 Apr 2017 14:15:38 -0700 Subject: [PATCH 368/440] add sort by match order sort by endpoint by default combine sort flags sort methods ignore HEAD and OPTIONS methods by default rearrange columns use format to build row format string rework tests add changelog --- CHANGES | 3 ++ flask/cli.py | 74 +++++++++++++++---------- tests/test_apps/cliapp/routesapp.py | 18 ------- tests/test_cli.py | 84 ++++++++++++++++++----------- 4 files changed, 104 insertions(+), 75 deletions(-) delete mode 100644 tests/test_apps/cliapp/routesapp.py diff --git a/CHANGES b/CHANGES index 11ac6430..ddab541f 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,8 @@ Major release, unreleased - ``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`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1898: https://github.com/pallets/flask/pull/1898 @@ -40,6 +42,7 @@ Major release, unreleased .. _#2223: https://github.com/pallets/flask/pull/2223 .. _#2254: https://github.com/pallets/flask/pull/2254 .. _#2256: https://github.com/pallets/flask/pull/2256 +.. _#2259: https://github.com/pallets/flask/pull/2259 Version 0.12.1 -------------- diff --git a/flask/cli.py b/flask/cli.py index 80aa1cd5..3d361be8 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -12,14 +12,17 @@ import os import sys import traceback -from threading import Lock, Thread from functools import update_wrapper +from operator import attrgetter +from threading import Lock, Thread import click +from . import __version__ from ._compat import iteritems, reraise +from .globals import current_app from .helpers import get_debug_flag -from . import __version__ + class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" @@ -485,34 +488,51 @@ def shell_command(): code.interact(banner=banner, local=ctx) -@click.command('routes', short_help='Show routes for the app.') -@click.option('-r', 'order_by', flag_value='rule', default=True, help='Order by route') -@click.option('-e', 'order_by', flag_value='endpoint', help='Order by endpoint') -@click.option('-m', 'order_by', flag_value='methods', help='Order by methods') +@click.command('routes', short_help='Show the routes for the app.') +@click.option( + '--sort', '-s', + type=click.Choice(('endpoint', 'methods', 'rule', 'match')), + default='endpoint', + help=( + 'Method to sort routes by. "match" is the order that Flask will match ' + 'routes when dispatching a request.' + ) +) +@click.option( + '--all-methods', + is_flag=True, + help="Show HEAD and OPTIONS methods." +) @with_appcontext -def routes_command(order_by): - """Show all routes with endpoints and methods.""" - from flask.globals import _app_ctx_stack - app = _app_ctx_stack.top.app - - order_key = lambda rule: getattr(rule, order_by) - sorted_rules = sorted(app.url_map.iter_rules(), key=order_key) - - max_rule = max(len(rule.rule) for rule in sorted_rules) - max_rule = max(max_rule, len('Route')) - max_ep = max(len(rule.endpoint) for rule in sorted_rules) - max_ep = max(max_ep, len('Endpoint')) - max_meth = max(len(', '.join(rule.methods)) for rule in sorted_rules) - max_meth = max(max_meth, len('Methods')) +def routes_command(sort, all_methods): + """Show all registered routes with endpoints and methods.""" + + rules = list(current_app.url_map.iter_rules()) + ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS')) + + if sort in ('endpoint', 'rule'): + rules = sorted(rules, key=attrgetter(sort)) + elif sort == 'methods': + rules = sorted(rules, key=lambda rule: sorted(rule.methods)) + + rule_methods = [ + ', '.join(sorted(rule.methods - ignored_methods)) for rule in rules + ] + + headers = ('Endpoint', 'Methods', 'Rule') + widths = ( + max(len(rule.endpoint) for rule in rules), + max(len(methods) for methods in rule_methods), + max(len(rule.rule) for rule in rules), + ) + widths = [max(len(h), w) for h, w in zip(headers, widths)] + row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths) - columnformat = '{:<%s} {:<%s} {:<%s}' % (max_rule, max_ep, max_meth) - click.echo(columnformat.format('Route', 'Endpoint', 'Methods')) - under_count = max_rule + max_ep + max_meth + 4 - click.echo('-' * under_count) + click.echo(row.format(*headers).strip()) + click.echo(row.format(*('-' * width for width in widths))) - for rule in sorted_rules: - methods = ', '.join(rule.methods) - click.echo(columnformat.format(rule.rule, rule.endpoint, methods)) + for rule, methods in zip(rules, rule_methods): + click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip()) cli = FlaskGroup(help="""\ diff --git a/tests/test_apps/cliapp/routesapp.py b/tests/test_apps/cliapp/routesapp.py deleted file mode 100644 index 84060546..00000000 --- a/tests/test_apps/cliapp/routesapp.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import absolute_import, print_function - -from flask import Flask - - -noroute_app = Flask('noroute app') -simpleroute_app = Flask('simpleroute app') -only_POST_route_app = Flask('GET route app') - - -@simpleroute_app.route('/simpleroute') -def simple(): - pass - - -@only_POST_route_app.route('/only-post', methods=['POST']) -def only_post(): - pass diff --git a/tests/test_cli.py b/tests/test_cli.py index 56ebce90..ab875cef 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -14,6 +14,7 @@ from __future__ import absolute_import, print_function import os import sys +from functools import partial import click import pytest @@ -195,7 +196,7 @@ def test_flaskgroup(runner): assert result.output == 'flaskgroup\n' -def test_print_exceptions(): +def test_print_exceptions(runner): """Print the stacktrace if the CLI.""" def create_app(info): raise Exception("oh no") @@ -205,7 +206,6 @@ def test_print_exceptions(): def cli(**params): pass - runner = CliRunner() result = runner.invoke(cli, ['--help']) assert result.exit_code == 0 assert 'Exception: oh no' in result.output @@ -213,34 +213,58 @@ def test_print_exceptions(): class TestRoutes: - def test_no_route(self, runner, monkeypatch): - monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:noroute_app') - result = runner.invoke(cli, ['routes'], catch_exceptions=False) - assert result.exit_code == 0 - assert result.output == """\ -Route Endpoint Methods ------------------------------------------------------ -/static/ static HEAD, OPTIONS, GET -""" + @pytest.fixture + def invoke(self, runner): + def create_app(info): + app = Flask(__name__) + app.testing = True - def test_simple_route(self, runner, monkeypatch): - monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:simpleroute_app') - result = runner.invoke(cli, ['routes'], catch_exceptions=False) - assert result.exit_code == 0 - assert result.output == """\ -Route Endpoint Methods ------------------------------------------------------ -/simpleroute simple HEAD, OPTIONS, GET -/static/ static HEAD, OPTIONS, GET -""" + @app.route('/get_post//', methods=['GET', 'POST']) + def yyy_get_post(x, y): + pass + + @app.route('/zzz_post', methods=['POST']) + def aaa_post(): + pass - def test_only_POST_route(self, runner, monkeypatch): - monkeypatch.setitem(os.environ, 'FLASK_APP', 'cliapp.routesapp:only_POST_route_app') - result = runner.invoke(cli, ['routes'], catch_exceptions=False) + return app + + cli = FlaskGroup(create_app=create_app) + return partial(runner.invoke, cli) + + def expect_order(self, order, output): + # skip the header and match the start of each row + for expect, line in zip(order, output.splitlines()[2:]): + # do this instead of startswith for nicer pytest output + assert line[:len(expect)] == expect + + def test_simple(self, invoke): + result = invoke(['routes']) assert result.exit_code == 0 - assert result.output == """\ -Route Endpoint Methods ------------------------------------------------------- -/only-post only_post POST, OPTIONS -/static/ static HEAD, OPTIONS, GET -""" + self.expect_order( + ['aaa_post', 'static', 'yyy_get_post'], + result.output + ) + + def test_sort(self, invoke): + default_output = invoke(['routes']).output + endpoint_output = invoke(['routes', '-s', 'endpoint']).output + assert default_output == endpoint_output + self.expect_order( + ['static', 'yyy_get_post', 'aaa_post'], + invoke(['routes', '-s', 'methods']).output + ) + self.expect_order( + ['yyy_get_post', 'static', 'aaa_post'], + invoke(['routes', '-s', 'rule']).output + ) + self.expect_order( + ['aaa_post', 'yyy_get_post', 'static'], + invoke(['routes', '-s', 'match']).output + ) + + def test_all_methods(self, invoke): + output = invoke(['routes']).output + assert 'GET, HEAD, OPTIONS, POST' not in output + output = invoke(['routes', '--all-methods']).output + assert 'GET, HEAD, OPTIONS, POST' in output From 6032c94aeb090c82ac99b9f886341f61c73b65d0 Mon Sep 17 00:00:00 2001 From: Benjamin Liebald Date: Wed, 2 Nov 2016 13:36:54 -0700 Subject: [PATCH 369/440] Mention existence of register_error_handler in errorpages.rst See https://github.com/pallets/flask/issues/1837 for context. --- docs/patterns/errorpages.rst | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/patterns/errorpages.rst b/docs/patterns/errorpages.rst index fccd4a6f..e7ec6b65 100644 --- a/docs/patterns/errorpages.rst +++ b/docs/patterns/errorpages.rst @@ -54,9 +54,11 @@ can be a different error: a handler for internal server errors will be passed other exception instances as well if they are uncaught. An error handler is registered with the :meth:`~flask.Flask.errorhandler` -decorator and the error code of the exception. Keep in mind that Flask -will *not* set the error code for you, so make sure to also provide the -HTTP status code when returning a response. +decorator and the error code of the exception (alternatively, you can use the +:meth:`~flask.Flask.register_error_handler` function, e.g., when you're +registering error handlers as part of your Application Factory). Keep in mind +that Flask will *not* set the error code for you, so make sure to also provide +the HTTP status code when returning a response.delete_cookie. Please note that if you add an error handler for "500 Internal Server Error", Flask will not trigger it if it's running in Debug mode. @@ -69,6 +71,18 @@ Here an example implementation for a "404 Page Not Found" exception:: def page_not_found(e): return render_template('404.html'), 404 +And, using an application factory pattern (see :ref:`app-factories`):: + + from flask import Flask, render_template + + def page_not_found(e): + return render_template('404.html'), 404 + + def create_app(config_filename): + app = Flask(__name__) + # ... + app.register_error_handler(404, page_not_found) + An example template might be this: .. sourcecode:: html+jinja From 011a4b1899fcb6da15a9d60c3a5c36a73ff92c46 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 30 Apr 2017 08:20:13 -0700 Subject: [PATCH 370/440] clean up error handler docs --- docs/patterns/errorpages.rst | 52 +++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/patterns/errorpages.rst b/docs/patterns/errorpages.rst index e7ec6b65..1df9c061 100644 --- a/docs/patterns/errorpages.rst +++ b/docs/patterns/errorpages.rst @@ -47,51 +47,53 @@ even if the application behaves correctly: Error Handlers -------------- -An error handler is a function, just like a view function, but it is -called when an error happens and is passed that error. The error is most -likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it -can be a different error: a handler for internal server errors will be -passed other exception instances as well if they are uncaught. +An error handler is a function that returns a response when a type of error is +raised, similar to how a view is a function that returns a response when a +request URL is matched. It is passed the instance of the error being handled, +which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. An error +handler for "500 Internal Server Error" will be passed uncaught exceptions in +addition to explicit 500 errors. An error handler is registered with the :meth:`~flask.Flask.errorhandler` -decorator and the error code of the exception (alternatively, you can use the -:meth:`~flask.Flask.register_error_handler` function, e.g., when you're -registering error handlers as part of your Application Factory). Keep in mind -that Flask will *not* set the error code for you, so make sure to also provide -the HTTP status code when returning a response.delete_cookie. +decorator or the :meth:`~flask.Flask.register_error_handler` method. A handler +can be registered for a status code, like 404, or for an exception class. -Please note that if you add an error handler for "500 Internal Server -Error", Flask will not trigger it if it's running in Debug mode. +The status code of the response will not be set to the handler's code. Make +sure to provide the appropriate HTTP status code when returning a response from +a handler. -Here an example implementation for a "404 Page Not Found" exception:: +A handler for "500 Internal Server Error" will not be used when running in +debug mode. Instead, the interactive debugger will be shown. + +Here is an example implementation for a "404 Page Not Found" exception:: from flask import render_template @app.errorhandler(404) def page_not_found(e): + # note that we set the 404 status explicitly return render_template('404.html'), 404 -And, using an application factory pattern (see :ref:`app-factories`):: +When using the :ref:`application factory pattern `:: from flask import Flask, render_template - + def page_not_found(e): return render_template('404.html'), 404 - + def create_app(config_filename): app = Flask(__name__) - # ... app.register_error_handler(404, page_not_found) + return app An example template might be this: .. sourcecode:: html+jinja - {% extends "layout.html" %} - {% block title %}Page Not Found{% endblock %} - {% block body %} -

      Page Not Found

      -

      What you were looking for is just not there. -

      go somewhere nice - {% endblock %} - + {% extends "layout.html" %} + {% block title %}Page Not Found{% endblock %} + {% block body %} +

      Page Not Found

      +

      What you were looking for is just not there. +

      go somewhere nice + {% endblock %} From 75b85656ddad86cbf0c3adb1128b758242793929 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 4 May 2017 18:24:04 -0700 Subject: [PATCH 371/440] optionally enable sphinxcontrib.log_cabinet collapses old changelog directives closes #1704 closes #1867 --- docs/conf.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index f53d72fa..47168c65 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,6 +38,14 @@ extensions = [ 'flaskdocext' ] +try: + __import__('sphinxcontrib.log_cabinet') +except ImportError: + print('sphinxcontrib-log-cabinet is not installed.') + print('Changelog directives will not be re-organized.') +else: + extensions.append('sphinxcontrib.log_cabinet') + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] From 160999e88241137c73aaa49e352dcbf2829fad5a Mon Sep 17 00:00:00 2001 From: wangbing Date: Wed, 10 May 2017 00:34:36 +0800 Subject: [PATCH 372/440] Removed unused import --- flask/json.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/json.py b/flask/json.py index bf8a8843..a029e73a 100644 --- a/flask/json.py +++ b/flask/json.py @@ -13,7 +13,6 @@ import uuid from datetime import date from .globals import current_app, request from ._compat import text_type, PY2 -from .ctx import has_request_context from werkzeug.http import http_date from jinja2 import Markup From dfb03c5673e97ee95289b844c13dbf506ea99e71 Mon Sep 17 00:00:00 2001 From: Xephyr826 Date: Wed, 10 May 2017 22:38:22 -0700 Subject: [PATCH 373/440] Improve Routing section Edited the entire section for clarity and concision. I rewrote sentences to make them shorter and to reduce ambiguity. Added a code sample to show the path converter type Removed the HTTP method overview. Although it was well written, the overview wasn't necessary in the quickstart. Readers can easily find an overview elsewhere. --- docs/quickstart.rst | 152 ++++++++++---------------------------------- 1 file changed, 34 insertions(+), 118 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 09365496..76f2af42 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -159,14 +159,9 @@ Have another debugger in mind? See :ref:`working-with-debuggers`. Routing ------- -Modern web applications have beautiful URLs. This helps people remember -the URLs, which is especially handy for applications that are used from -mobile devices with slower network connections. If the user can directly -go to the desired page without having to hit the index page it is more -likely they will like the page and come back next time. +Modern web applications use meaningful URLs to help users. Users are more likely to like a page and come back if the page uses a meaningful URL they can remember and use to directly visit a page. -As you have seen above, the :meth:`~flask.Flask.route` decorator is used to -bind a function to a URL. Here are some basic examples:: +As you saw in the minimal application, you can use the :meth:`~flask.Flask.route` decorator to bind a function to a meaningful URL. For example:: @app.route('/') def index(): @@ -176,16 +171,12 @@ bind a function to a URL. Here are some basic examples:: def hello(): return 'Hello, World' -But there is more to it! You can make certain parts of the URL dynamic and -attach multiple rules to a function. +You can do more! You can make parts of the URL dynamic and attach multiple rules to a function. Variable Rules `````````````` -To add variable parts to a URL you can mark these special sections as -````. Such a part is then passed as a keyword argument to your -function. Optionally a converter can be used by specifying a rule with -````. Here are some nice examples:: +You can add variable sections to a URL by marking sections with ````. Your function then receives the ```` as a keyword argument. Optionally, you can use a converter to specify a rule with ````. For example:: @app.route('/user/') def show_user_profile(username): @@ -197,22 +188,25 @@ function. Optionally a converter can be used by specifying a rule with # show the post with the given id, the id is an integer return 'Post %d' % post_id -The following converters exist: + @app.route('/path/') + def show_subpath(subpath): + # show the subpath after /path/ + return 'Subpath %s' % subpath + +Converter types: =========== =============================================== -`string` accepts any text without a slash (the default) +`string` (default) accepts any text without a slash `int` accepts integers -`float` like ``int`` but for floating point values -`path` like the default but also accepts slashes +`float` accepts floating point values +`path` like `string` but also accepts slashes `any` matches one of the items provided `uuid` accepts UUID strings =========== =============================================== .. admonition:: Unique URLs / Redirection Behavior - Flask's URL rules are based on Werkzeug's routing module. The idea - behind that module is to ensure beautiful and unique URLs based on - precedents laid down by Apache and earlier HTTP servers. + Flask's URL rules are based on Werkzeug's routing module. The routing module creates meaningful and unique URLs based on precedents laid down by Apache and earlier HTTP servers. Take these two rules:: @@ -224,15 +218,9 @@ The following converters exist: def about(): return 'The about page' - Though they look rather similar, they differ in their use of the trailing - slash in the URL *definition*. In the first case, the canonical URL for the - ``projects`` endpoint has a trailing slash. In that sense, it is similar to - a folder on a filesystem. Accessing it without a trailing slash will cause - Flask to redirect to the canonical URL with the trailing slash. + Though they look similar, they differ in their use of the trailing slash in the URL *definition*. In the first case, the canonical URL for the ``projects`` endpoint uses a trailing slash. It's similar to a folder in a file system, and 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 is defined without a trailing slash, - rather like the pathname of a file on UNIX-like systems. Accessing the URL - with a trailing slash will produce a 404 "Not Found" error. + 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. This behavior allows relative URLs to continue working even if the trailing slash is omitted, consistent with how Apache and other servers work. Also, @@ -245,12 +233,19 @@ The following converters exist: URL Building ```````````` -If it can match URLs, can Flask also generate them? Of course it can. To -build a URL to a specific function you can use the :func:`~flask.url_for` -function. It accepts the name of the function as first argument and a number -of keyword arguments, each corresponding to the variable part of the URL rule. -Unknown variable parts are appended to the URL as query parameters. Here are -some examples:: +Flask can match URLs, but can it also generate them? Yes! To build a URL to a specific function, you can use the :func:`~flask.url_for` function. It accepts the name of the function as its first argument and any number of keyword arguments, each corresponding to a variable part of the URL rule. Unknown variable parts are appended to the URL as query parameters. + +Why would you want to build URLs using the URL reversing function +:func:`~flask.url_for` instead of hard-coding them into your templates? Three good reasons: + +1. Reversing is often more descriptive than hard-coding the URLs. More importantly, you can change your URLs in one go instead of needing to remember to manually change hard-coded URLs. +2. URL building handles escaping of special characters and Unicode data transparently. +3. If your application is placed outside the URL root, for example, in ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly handles that for you. + + +For example, here we use the :meth:`~flask.Flask.test_request_context` method to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context` tells Flask to behave as though it's handling a request even while we use a Python shell. Also see, :ref:`context-locals`. + +Example:: >>> from flask import Flask, url_for >>> app = Flask(__name__) @@ -274,98 +269,19 @@ some examples:: /login?next=/ /user/John%20Doe -(This also uses the :meth:`~flask.Flask.test_request_context` method, explained -below. It tells Flask to behave as though it is handling a request, even -though we are interacting with it through a Python shell. Have a look at the -explanation below. :ref:`context-locals`). - -Why would you want to build URLs using the URL reversing function -:func:`~flask.url_for` instead of hard-coding them into your templates? -There are three good reasons for this: - -1. Reversing is often more descriptive than hard-coding the URLs. More - importantly, it allows you to change URLs in one go, without having to - remember to change URLs all over the place. -2. URL building will handle escaping of special characters and Unicode - data transparently for you, so you don't have to deal with them. -3. If your application is placed outside the URL root - say, in - ``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle - that properly for you. - - HTTP Methods ```````````` -HTTP (the protocol web applications are speaking) knows different methods for -accessing URLs. By default, a route only answers to ``GET`` requests, but that -can be changed by providing the ``methods`` argument to the -:meth:`~flask.Flask.route` decorator. Here are some examples:: - - from flask import request +Web application communicate using HTTP and use different methods for accessing URLs. You should familiarize yourself with the HTTP methods as you work with Flask. By default, a route only answers to ``GET`` requests. You can use the ``methods`` argument of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods. For example:: @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': - return do_the_login() + do_the_login() else: - return show_the_login_form() - -If ``GET`` is present, ``HEAD`` will be added automatically for you. You -don't have to deal with that. It will also make sure that ``HEAD`` requests -are handled as the `HTTP RFC`_ (the document describing the HTTP -protocol) demands, so you can completely ignore that part of the HTTP -specification. Likewise, as of Flask 0.6, ``OPTIONS`` is implemented for you -automatically as well. - -You have no idea what an HTTP method is? Worry not, here is a quick -introduction to HTTP methods and why they matter: - -The HTTP method (also often called "the verb") tells the server what the -client wants to *do* with the requested page. The following methods are -very common: - -``GET`` - The browser tells the server to just *get* the information stored on - that page and send it. This is probably the most common method. - -``HEAD`` - The browser tells the server to get the information, but it is only - interested in the *headers*, not the content of the page. An - application is supposed to handle that as if a ``GET`` request was - received but to not deliver the actual content. In Flask you don't - have to deal with that at all, the underlying Werkzeug library handles - that for you. - -``POST`` - The browser tells the server that it wants to *post* some new - information to that URL and that the server must ensure the data is - stored and only stored once. This is how HTML forms usually - transmit data to the server. - -``PUT`` - Similar to ``POST`` but the server might trigger the store procedure - multiple times by overwriting the old values more than once. Now you - might be asking why this is useful, but there are some good reasons - to do it this way. Consider that the connection is lost during - transmission: in this situation a system between the browser and the - server might receive the request safely a second time without breaking - things. With ``POST`` that would not be possible because it must only - be triggered once. - -``DELETE`` - Remove the information at the given location. - -``OPTIONS`` - Provides a quick way for a client to figure out which methods are - supported by this URL. Starting with Flask 0.6, this is implemented - for you automatically. - -Now the interesting part is that in HTML4 and XHTML1, the only methods a -form can submit to the server are ``GET`` and ``POST``. But with JavaScript -and future HTML standards you can use the other methods as well. Furthermore -HTTP has become quite popular lately and browsers are no longer the only -clients that are using HTTP. For instance, many revision control systems -use it. + 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, as of Flask 0.6, ``OPTIONS`` is automatically implemented for you. .. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt From a80eab70ba96d7d43c2ca5dc742597320aebe104 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 May 2017 12:35:03 -0700 Subject: [PATCH 374/440] Update CONTRIBUTING.rst basic pytest can't handle examples, pass tests dir --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 66766512..852e44be 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -54,7 +54,7 @@ Install Flask as an editable package using the current source:: Then you can run the testsuite with:: - pytest + pytest tests/ With only pytest installed, a large part of the testsuite will get skipped though. Whether this is relevant depends on which part of Flask you're working From 1fb43e3be47ab24cc32396e28a6ad3734e9109e9 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 May 2017 13:54:37 -0700 Subject: [PATCH 375/440] rewrite installation docs discuss python version discuss all dependencies prefer python 3 in instructions [ci skip] --- docs/installation.rst | 231 ++++++++++++++++++++---------------------- 1 file changed, 108 insertions(+), 123 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 38094ded..cd869b9a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -3,188 +3,173 @@ Installation ============ -Flask depends on some external libraries, like `Werkzeug -`_ and `Jinja2 `_. -Werkzeug is a toolkit for WSGI, the standard Python interface between web -applications and a variety of servers for both development and deployment. -Jinja2 renders templates. +Python Version +-------------- -So how do you get all that on your computer quickly? There are many ways you -could do that, but the most kick-ass method is virtualenv, so let's have a look -at that first. +We recommend using the latest version of Python 3. Flask supports Python 3.3 +and newer, Python 2.6 and newer, and PyPy. -You will need Python 2.6 or newer to get started, so be sure to have an -up-to-date Python 2.x installation. For using Flask with Python 3 have a -look at :ref:`python3-support`. +Dependencies +------------ -.. _virtualenv: +These distributions will be installed automatically when installing Flask. -virtualenv ----------- +* `Werkzeug`_ implements WSGI, the standard Python interface between + applications and servers. +* `Jinja`_ is a template language that renders the pages your application + serves. +* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering + templates to avoid injection attacks. +* `ItsDangerous`_ securely signs data to ensure its integrity. This is used + to protect Flask's session cookie. +* `Click`_ is a framework for writing command line applications. It provides + the ``flask`` command and allows adding custom management commands. -Virtualenv is probably what you want to use during development, and if you have -shell access to your production machines, you'll probably want to use it there, -too. +.. _Werkzeug: http://werkzeug.pocoo.org/ +.. _Jinja: http://jinja.pocoo.org/ +.. _MarkupSafe: https://pypi.python.org/pypi/MarkupSafe +.. _ItsDangerous: https://pythonhosted.org/itsdangerous/ +.. _Click: http://click.pocoo.org/ -What problem does virtualenv solve? If you like Python as much as I do, -chances are you want to use it for other projects besides Flask-based web -applications. But the more projects you have, the more likely it is that you -will be working with different versions of Python itself, or at least different -versions of Python libraries. Let's face it: quite often libraries break -backwards compatibility, and it's unlikely that any serious application will -have zero dependencies. So what do you do if two or more of your projects have -conflicting dependencies? +Optional dependencies +~~~~~~~~~~~~~~~~~~~~~ -Virtualenv to the rescue! Virtualenv enables multiple side-by-side -installations of Python, one for each project. It doesn't actually install -separate copies of Python, but it does provide a clever way to keep different -project environments isolated. Let's see how virtualenv works. +These distributions will not be installed automatically. Flask will detect and +use them if you install them. +* `Blinker`_ provides support for :ref:`signals`. +* `SimpleJSON`_ is a fast JSON implementation that is compatible with + Python's ``json`` module. It is preferred for JSON operations if it is + installed. -.. admonition:: A note on python3 and virtualenv +.. _Blinker: https://pythonhosted.org/blinker/ +.. _SimpleJSON: https://simplejson.readthedocs.io/ - If you are planning on using python3 with the virtualenv, you don't need to - install ``virtualenv``. Python3 has built-in support for virtual environments. +Virtual environments +-------------------- -If you are on Mac OS X or Linux, chances are that the following -command will work for you:: +Use a virtual environment to manage the dependencies for your project, both in +development and in production. - $ sudo pip install virtualenv +What problem does a virtual environment solve? The more Python projects you +have, the more likely it is that you need to work with different versions of +Python libraries, or even Python itself. Newer versions of libraries for one +project can break compatibility in another project. -It will probably install virtualenv on your system. Maybe it's even -in your package manager. If you use Ubuntu, try:: +Virtual environments are independent groups of Python libraries, one for each +project. Packages installed for one project will not affect other projects or +the operating system's packages. - $ sudo apt-get install python-virtualenv +Python 3 comes bundled with the :mod:`venv` module to create virtual +environments. If you're using a modern version of Python, you can continue on +to the next section. -If you are on Windows and don't have the ``easy_install`` command, you must -install it first. Check the :ref:`windows-easy-install` section for more -information about how to do that. Once you have it installed, run the same -commands as above, but without the ``sudo`` prefix. +If you're using Python 2, see :ref:`install-install-virtualenv` first. -Creating a virtual environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _install-create-env: -Once you have virtualenv installed, just fire up a shell and create -your own environment. I usually create a project folder and a :file:`virtenv` -folder within:: +Create an environment +~~~~~~~~~~~~~~~~~~~~~ - $ mkdir myproject - $ cd myproject +Create a project folder and a :file:`venv` folder within: -There is a little change in how you create a virtualenv depending on which python-version you are currently using. +.. code-block:: sh -**Python2** + mkdir myproject + cd myproject + python3 -m venv venv -:: +On Windows: - $ virtualenv virtenv - New python executable in virtenv/bin/python - Installing setuptools, pip............done. +.. code-block:: bat -**Python 3.6 and above** + py -3 -m venv venv -:: +If you needed to install virtualenv because you are on an older version of +Python, use the following command instead: - $ python3 -m venv virtenv +.. code-block:: sh -Activating a virtual environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + virtualenv venv -Now, whenever you want to work on a project, you only have to activate the -corresponding environment. On OS X and Linux, do the following:: +On Windows: - $ . virtenv/bin/activate +.. code-block:: bat -If you are a Windows user, the following command is for you:: + \Python27\Scripts\virtualenv.exe venv - $ virtenv\Scripts\activate +Activate the environment +~~~~~~~~~~~~~~~~~~~~~~~~ -Either way, you should now be using your virtualenv (notice how the prompt of -your shell has changed to show the active environment). +Before you work on your project, activate the corresponding environment: -And if you want to go back to the real world, use the following command:: +.. code-block:: sh - $ deactivate + . venv/bin/activate -After doing this, the prompt of your shell should be as familiar as before. +On Windows: -Now, let's move on. Enter the following command to get Flask activated in your -virtualenv:: +.. code-block:: bat - $ pip install Flask + venv\Scripts\activate -A few seconds later and you are good to go. +Your shell prompt will change to show the name of the activated environment. +Install Flask +------------- -System-Wide Installation ------------------------- +Within the activated environment, use the following command to install Flask: -This is possible as well, though I do not recommend it. Just run -``pip`` with root privileges:: +.. code-block:: sh - $ sudo pip install Flask + pip install Flask -(On Windows systems, run it in a command-prompt window with administrator -privileges, and leave out ``sudo``.) +Living on the edge +~~~~~~~~~~~~~~~~~~ +If you want to work with the latest Flask code before it's released, install or +update the code from the master branch: -Living on the Edge ------------------- +.. code-block:: sh -If you want to work with the latest version of Flask, there are two ways: you -can either let ``pip`` pull in the development version, or you can tell -it to operate on a git checkout. Either way, virtualenv is recommended. + pip install -U https://github.com/pallets/flask/archive/master.tar.gz -Get the git checkout in a new virtualenv and run in development mode:: +.. _install-install-virtualenv: - $ git clone https://github.com/pallets/flask.git - Initialized empty Git repository in ~/dev/flask/.git/ - $ cd flask - $ virtualenv virtenv - New python executable in virtenv/bin/python - Installing setuptools, pip............done. - $ . virtenv/bin/activate - $ python setup.py develop - ... - Finished processing dependencies for Flask +Install virtualenv +------------------ -This will pull in the dependencies and activate the git head as the current -version inside the virtualenv. Then all you have to do is run ``git pull -origin`` to update to the latest version. +If you are using Python 2, the venv module is not available. Instead, +install `virtualenv`_. -.. _windows-easy-install: +On Linux, virtualenv is provided by your package manager: -`pip` and `setuptools` on Windows ---------------------------------- +.. code-block:: sh -Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools`` -and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial -package you will need is pip - this will let you install -anything else (like virtualenv). Fortunately there is a "bootstrap script" -you can run to install. + # Debian, Ubuntu + sudo apt-get install python-virtualenv -If you don't currently have ``pip``, then `get-pip.py` will install it for you. + # CentOS, Fedora + sudo yum install python-virtualenv -`get-pip.py`_ + # Arch + sudo pacman -S python-virtualenv -It should be double-clickable once you download it. If you already have ``pip``, -you can upgrade them by running:: +If you are on Mac OS X or Windows, download `get-pip.py`_, then: - > pip install --upgrade pip setuptools +.. code-block:: sh -Most often, once you pull up a command prompt you want to be able to type ``pip`` -and ``python`` which will run those things, but this might not automatically happen -on Windows, because it doesn't know where those executables are (give either a try!). + sudo python2 Downloads/get-pip.py + sudo python2 -m pip install virtualenv -To fix this, you should be able to navigate to your Python install directory -(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the -:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and -check that you can now just type ``python`` to bring up the interpreter. +On Windows, as an administrator: -Finally, to install `virtualenv`_, you can simply run:: +.. code-block:: bat - > pip install virtualenv + \Python27\python.exe Downloads\get-pip.py + \Python27\python.exe -m pip install virtualenv -Then you can be off on your way following the installation instructions above. +Now you can continue to :ref:`install-create-env`. +.. _virtualenv: https://virtualenv.pypa.io/ .. _get-pip.py: https://bootstrap.pypa.io/get-pip.py From 2592f927a00f39ca089f67144a1e7f200ad213d1 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 May 2017 22:31:19 -0700 Subject: [PATCH 376/440] wrap lines tighten up wording remove any converter from quickstart use correct rst code syntax --- docs/quickstart.rst | 148 ++++++++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 76f2af42..7bdb67e4 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -159,9 +159,11 @@ Have another debugger in mind? See :ref:`working-with-debuggers`. Routing ------- -Modern web applications use meaningful URLs to help users. Users are more likely to like a page and come back if the page uses a meaningful URL they can remember and use to directly visit a page. +Modern web applications use meaningful URLs to help users. Users are more +likely to like a page and come back if the page uses a meaningful URL they can +remember and use to directly visit a page. -As you saw in the minimal application, you can use the :meth:`~flask.Flask.route` decorator to bind a function to a meaningful URL. For example:: +Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. :: @app.route('/') def index(): @@ -171,12 +173,16 @@ As you saw in the minimal application, you can use the :meth:`~flask.Flask.route def hello(): return 'Hello, World' -You can do more! You can make parts of the URL dynamic and attach multiple rules to a function. +You can do more! You can make parts of the URL dynamic and attach multiple +rules to a function. Variable Rules `````````````` -You can add variable sections to a URL by marking sections with ````. Your function then receives the ```` as a keyword argument. Optionally, you can use a converter to specify a rule with ````. For example:: +You can add variable sections to a URL by marking sections with +````. Your function then receives the ```` +as a keyword argument. Optionally, you can use a converter to specify the type +of the argument like ````. :: @app.route('/user/') def show_user_profile(username): @@ -195,75 +201,91 @@ You can add variable sections to a URL by marking sections with ``>> from flask import Flask, url_for - >>> app = Flask(__name__) - >>> @app.route('/') - ... def index(): pass - ... - >>> @app.route('/login') - ... def login(): pass - ... - >>> @app.route('/user/') - ... def profile(username): pass - ... - >>> with app.test_request_context(): - ... print(url_for('index')) - ... print(url_for('login')) - ... print(url_for('login', next='/')) - ... print(url_for('profile', username='John Doe')) - ... +:func:`~flask.url_for` instead of hard-coding them into your templates? + +1. Reversing is often more descriptive than hard-coding the URLs. +2. You can change your URLs in one go instead of needing to remember to + 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 + ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly + handles that for you. + +For example, here we use the :meth:`~flask.Flask.test_request_context` method +to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context` +tells Flask to behave as though it's handling a request even while we use a +Python shell. See :ref:`context-locals`. :: + + from flask import Flask, url_for + + app = Flask(__name__) + + @app.route('/') + def index(): + return 'index' + + @app.route('/login') + def login(): + return 'login' + + @app.route('/user/') + def profile(username): + return '{}'s profile'.format(username) + + with app.test_request_context(): + print(url_for('index')) + print(url_for('login')) + print(url_for('login', next='/')) + print(url_for('profile', username='John Doe')) + / /login /login?next=/ @@ -272,7 +294,11 @@ Example:: HTTP Methods ```````````` -Web application communicate using HTTP and use different methods for accessing URLs. You should familiarize yourself with the HTTP methods as you work with Flask. By default, a route only answers to ``GET`` requests. You can use the ``methods`` argument of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods. For example:: +Web applications use different HTTP methods when accessing URLs. You should +familiarize yourself with the HTTP methods as you work with Flask. By default, +a route only answers to ``GET`` requests. You can use the ``methods`` argument +of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods. +:: @app.route('/login', methods=['GET', 'POST']) def login(): @@ -281,7 +307,9 @@ Web application communicate using HTTP and use different methods for accessing U else: 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, as of Flask 0.6, ``OPTIONS`` is automatically implemented for you. +If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method +and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise, +``OPTIONS`` is automatically implemented for you. .. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt From cc59f2b2046fdc5ef280b95aaa22c26c3e0fba46 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 11 May 2017 22:48:21 -0700 Subject: [PATCH 377/440] clean up deferred callback doc remove doc about writing after_this_request [ci skip] --- docs/patterns/deferredcallbacks.rst | 80 ++++++++++------------------- 1 file changed, 26 insertions(+), 54 deletions(-) diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst index a79dc913..bc20cdd6 100644 --- a/docs/patterns/deferredcallbacks.rst +++ b/docs/patterns/deferredcallbacks.rst @@ -3,71 +3,43 @@ Deferred Request Callbacks ========================== -One of the design principles of Flask is that response objects are created -and passed down a chain of potential callbacks that can modify them or -replace them. When the request handling starts, there is no response -object yet. It is created as necessary either by a view function or by -some other component in the system. - -But what happens if you want to modify the response at a point where the -response does not exist yet? A common example for that would be a -before-request function that wants to set a cookie on the response object. - -One way is to avoid the situation. Very often that is possible. For -instance you can try to move that logic into an after-request callback -instead. Sometimes however moving that code there is just not a very -pleasant experience or makes code look very awkward. - -As an alternative possibility you can attach a bunch of callback functions -to the :data:`~flask.g` object and call them at the end of the request. -This way you can defer code execution from anywhere in the application. - - -The Decorator -------------- - -The following decorator is the key. It registers a function on a list on -the :data:`~flask.g` object:: - - from flask import g - - def after_this_request(f): - if not hasattr(g, 'after_request_callbacks'): - g.after_request_callbacks = [] - g.after_request_callbacks.append(f) - return f - - -Calling the Deferred --------------------- - -Now you can use the `after_this_request` decorator to mark a function to -be called at the end of the request. But we still need to call them. For -this the following function needs to be registered as -:meth:`~flask.Flask.after_request` callback:: - - @app.after_request - def call_after_request_callbacks(response): - for callback in getattr(g, 'after_request_callbacks', ()): - callback(response) - return response - - -A Practical Example -------------------- +One of the design principles of Flask is that response objects are created and +passed down a chain of potential callbacks that can modify them or replace +them. When the request handling starts, there is no response object yet. It is +created as necessary either by a view function or by some other component in +the system. + +What happens if you want to modify the response at a point where the response +does not exist yet? A common example for that would be a +:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the +response object. + +One way is to avoid the situation. Very often that is possible. For instance +you can try to move that logic into a :meth:`~flask.Flask.after_request` +callback instead. However, sometimes moving code there makes it more +more complicated or awkward to reason about. + +As an alternative, you can use :func:`~flask.after_this_request` to register +callbacks that will execute after only the current request. This way you can +defer code execution from anywhere in the application, based on the current +request. At any time during a request, we can register a function to be called at the -end of the request. For example you can remember the current language of the -user in a cookie in the before-request function:: +end of the request. For example you can remember the current language of the +user in a cookie in a :meth:`~flask.Flask.before_request` callback:: from flask import request, after_this_request @app.before_request def detect_user_language(): language = request.cookies.get('user_lang') + if language is None: language = guess_language_from_request() + + # when the response exists, set a cookie with the language @after_this_request def remember_language(response): response.set_cookie('user_lang', language) + g.language = language From c3d49e29ea42d2f468bc780c15683ca197b50d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Sun, 4 Dec 2016 18:29:11 +0000 Subject: [PATCH 378/440] show warning if session cookie domain is ip closes #2007 --- flask/helpers.py | 20 ++++++++++++++++++++ flask/sessions.py | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 828f5840..4794ef97 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -976,3 +976,23 @@ def total_seconds(td): :rtype: int """ return td.days * 60 * 60 * 24 + td.seconds + +def is_ip(ip): + """Returns the if the string received is an IP or not. + + :param string: the string to check if it an IP or not + :param var_name: the name of the string that is being checked + + :returns: True if string is an IP, False if not + :rtype: boolean + """ + import socket + + for family in (socket.AF_INET, socket.AF_INET6): + try: + socket.inet_pton(family, ip) + except socket.error: + pass + else: + return True + return False diff --git a/flask/sessions.py b/flask/sessions.py index 525ff246..f1729674 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -11,13 +11,14 @@ import uuid import hashlib +from warnings import warn from base64 import b64encode, b64decode from datetime import datetime from werkzeug.http import http_date, parse_date from werkzeug.datastructures import CallbackDict from . import Markup, json from ._compat import iteritems, text_type -from .helpers import total_seconds +from .helpers import total_seconds, is_ip from itsdangerous import URLSafeTimedSerializer, BadSignature @@ -336,6 +337,9 @@ class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): domain = self.get_cookie_domain(app) + if domain is not None: + if is_ip(domain): + warnings.warn("IP introduced in SESSION_COOKIE_DOMAIN", RuntimeWarning) path = self.get_cookie_path(app) # Delete case. If there is no session we bail early. From f75ad9fca2e17c4777a3d7efc65f3ccab261e22c Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 13 May 2017 21:31:46 -0700 Subject: [PATCH 379/440] refactor session cookie domain logic cache result of session cookie domain add warnings for session cookie domain issues add changelog --- CHANGES | 7 ++++ flask/helpers.py | 18 +++++----- flask/sessions.py | 83 ++++++++++++++++++++++++++++++--------------- tests/test_basic.py | 36 ++++++++++++++++++++ 4 files changed, 109 insertions(+), 35 deletions(-) diff --git a/CHANGES b/CHANGES index ddab541f..a6eceb87 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,12 @@ Major release, unreleased 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`_) +- Allow IP address as exact session cookie domain. (`#2282`_) +- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``. + (`#2282`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1898: https://github.com/pallets/flask/pull/1898 @@ -43,6 +49,7 @@ Major release, unreleased .. _#2254: https://github.com/pallets/flask/pull/2254 .. _#2256: https://github.com/pallets/flask/pull/2256 .. _#2259: https://github.com/pallets/flask/pull/2259 +.. _#2282: https://github.com/pallets/flask/pull/2282 Version 0.12.1 -------------- diff --git a/flask/helpers.py b/flask/helpers.py index 4794ef97..51c3b064 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -10,6 +10,7 @@ """ import os +import socket import sys import pkgutil import posixpath @@ -977,22 +978,23 @@ def total_seconds(td): """ return td.days * 60 * 60 * 24 + td.seconds -def is_ip(ip): - """Returns the if the string received is an IP or not. - :param string: the string to check if it an IP or not - :param var_name: the name of the string that is being checked +def is_ip(value): + """Determine if the given string is an IP address. - :returns: True if string is an IP, False if not - :rtype: boolean + :param value: value to check + :type value: str + + :return: True if string is an IP address + :rtype: bool """ - import socket for family in (socket.AF_INET, socket.AF_INET6): try: - socket.inet_pton(family, ip) + socket.inet_pton(family, value) except socket.error: pass else: return True + return False diff --git a/flask/sessions.py b/flask/sessions.py index f1729674..9fef6a9d 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -11,7 +11,7 @@ import uuid import hashlib -from warnings import warn +import warnings from base64 import b64encode, b64decode from datetime import datetime from werkzeug.http import http_date, parse_date @@ -201,30 +201,62 @@ class SessionInterface(object): return isinstance(obj, self.null_session_class) def get_cookie_domain(self, app): - """Helpful helper method that returns the cookie domain that should - be used for the session cookie if session cookies are used. + """Returns the domain that should be set for the session cookie. + + Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise + falls back to detecting the domain based on ``SERVER_NAME``. + + Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is + updated to avoid re-running the logic. """ - if app.config['SESSION_COOKIE_DOMAIN'] is not None: - return app.config['SESSION_COOKIE_DOMAIN'] - if app.config['SERVER_NAME'] is not None: - # chop off the port which is usually not supported by browsers - rv = '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] - - # Google chrome does not like cookies set to .localhost, so - # we just go with no domain then. Flask documents anyways that - # cross domain cookies need a fully qualified domain name - if rv == '.localhost': - rv = None - - # If we infer the cookie domain from the server name we need - # to check if we are in a subpath. In that case we can't - # set a cross domain cookie. - if rv is not None: - path = self.get_cookie_path(app) - if path != '/': - rv = rv.lstrip('.') - - return rv + + rv = app.config['SESSION_COOKIE_DOMAIN'] + + # set explicitly, or cached from SERVER_NAME detection + # if False, return None + if rv is not None: + return rv if rv else None + + rv = app.config['SERVER_NAME'] + + # server name not set, cache False to return none next time + if not rv: + app.config['SESSION_COOKIE_DOMAIN'] = False + return None + + # chop off the port which is usually not supported by browsers + # remove any leading '.' since we'll add that later + rv = rv.rsplit(':', 1)[0].lstrip('.') + + if '.' not in rv: + # Chrome doesn't allow names without a '.' + # this should only come up with localhost + # hack around this by not setting the name, and show a warning + warnings.warn( + '"{rv}" is not a valid cookie domain, it must contain a ".".' + ' Add an entry to your hosts file, for example' + ' "{rv}.localdomain", and use that instead.'.format(rv=rv) + ) + app.config['SESSION_COOKIE_DOMAIN'] = False + return None + + ip = is_ip(rv) + + if ip: + warnings.warn( + 'The session cookie domain is an IP address. This may not work' + ' as intended in some browsers. Add an entry to your hosts' + ' file, for example "localhost.localdomain", and use that' + ' instead.' + ) + + # if this is not an ip and app is mounted at the root, allow subdomain + # matching by adding a '.' prefix + if self.get_cookie_path(app) == '/' and not ip: + rv = '.' + rv + + app.config['SESSION_COOKIE_DOMAIN'] = rv + return rv def get_cookie_path(self, app): """Returns the path for which the cookie should be valid. The @@ -337,9 +369,6 @@ class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): domain = self.get_cookie_domain(app) - if domain is not None: - if is_ip(domain): - warnings.warn("IP introduced in SESSION_COOKIE_DOMAIN", RuntimeWarning) path = self.get_cookie_path(app) # Delete case. If there is no session we bail early. diff --git a/tests/test_basic.py b/tests/test_basic.py index 163b83cf..80091bd6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -351,6 +351,42 @@ def test_session_using_session_settings(): assert 'httponly' not in cookie +def test_session_localhost_warning(recwarn): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='testing', + SERVER_NAME='localhost:5000', + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'testing' + + rv = app.test_client().get('/', 'http://localhost:5000/') + assert 'domain' not in rv.headers['set-cookie'].lower() + w = recwarn.pop(UserWarning) + assert '"localhost" is not a valid cookie domain' in str(w.message) + + +def test_session_ip_warning(recwarn): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='testing', + SERVER_NAME='127.0.0.1:5000', + ) + + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'testing' + + rv = app.test_client().get('/', 'http://127.0.0.1:5000/') + assert 'domain=127.0.0.1' in rv.headers['set-cookie'].lower() + w = recwarn.pop(UserWarning) + assert 'cookie domain is an IP' in str(w.message) + + def test_missing_session(): app = flask.Flask(__name__) From d5a88bf0d36a2648cfc224884a144df7870894ab Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 15 May 2017 12:40:09 -0700 Subject: [PATCH 380/440] explain when to use a task queue remove deprecated abstract attr from celery add explanation of example task [ci skip] --- docs/patterns/celery.rst | 78 ++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index 548da29b..c3098a9e 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -1,24 +1,27 @@ -Celery Based Background Tasks -============================= +Celery Background Tasks +======================= -Celery is a task queue for Python with batteries included. It used to -have a Flask integration but it became unnecessary after some -restructuring of the internals of Celery with Version 3. This guide fills -in the blanks in how to properly use Celery with Flask but assumes that -you generally already read the `First Steps with Celery -`_ -guide in the official Celery documentation. +If your application has a long running task, such as processing some uploaded +data or sending email, you don't want to wait for it to finish during a +request. Instead, use a task queue to send the necessary data to another +process that will run the task in the background while the request returns +immediately. -Installing Celery ------------------ +Celery is a powerful task queue that can be used for simple background tasks +as well as complex multi-stage programs and schedules. This guide will show you +how to configure Celery using Flask, but assumes you've already read the +`First Steps with Celery `_ +guide in the Celery documentation. -Celery is on the Python Package Index (PyPI), so it can be installed with -standard Python tools like :command:`pip` or :command:`easy_install`:: +Install +------- + +Celery is a separate Python package. Install it from PyPI using pip:: $ pip install celery -Configuring Celery ------------------- +Configure +--------- The first thing you need is a Celery instance, this is called the celery application. It serves the same purpose as the :class:`~flask.Flask` @@ -36,15 +39,18 @@ This is all that is necessary to properly integrate Celery with Flask:: from celery import Celery def make_celery(app): - celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'], - broker=app.config['CELERY_BROKER_URL']) + celery = Celery( + app.import_name, + backend=app.config['CELERY_RESULT_BACKEND'], + broker=app.config['CELERY_BROKER_URL'] + ) celery.conf.update(app.config) - TaskBase = celery.Task - class ContextTask(TaskBase): - abstract = True + + class ContextTask(celery.Task): def __call__(self, *args, **kwargs): with app.app_context(): return self.run(*args, **kwargs) + celery.Task = ContextTask return celery @@ -53,11 +59,12 @@ from the application config, updates the rest of the Celery config from the Flask config and then creates a subclass of the task that wraps the task execution in an application context. -Minimal Example +An example task --------------- -With what we have above this is the minimal example of using Celery with -Flask:: +Let's write a task that adds two numbers together and returns the result. We +configure Celery's broker and backend to use Redis, create a ``celery`` +application using the factor from above, and then use it to define the task. :: from flask import Flask @@ -68,26 +75,27 @@ Flask:: ) celery = make_celery(flask_app) - @celery.task() def add_together(a, b): return a + b -This task can now be called in the background: +This task can now be called in the background:: ->>> result = add_together.delay(23, 42) ->>> result.wait() -65 + result = add_together.delay(23, 42) + result.wait() # 65 -Running the Celery Worker -------------------------- +Run a worker +------------ -Now if you jumped in and already executed the above code you will be -disappointed to learn that your ``.wait()`` will never actually return. -That's because you also need to run celery. You can do that by running -celery as a worker:: +If you jumped in and already executed the above code you will be +disappointed to learn that ``.wait()`` will never actually return. +That's because you also need to run a Celery worker to receive and execute the +task. :: $ celery -A your_application.celery worker The ``your_application`` string has to point to your application's package -or module that creates the `celery` object. +or module that creates the ``celery`` object. + +Now that the worker is running, ``wait`` will return the result once the task +is finished. From 2a6579430649d6514384ca592730c6995e140c43 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 15 May 2017 16:58:01 -0700 Subject: [PATCH 381/440] safe_join on Windows uses posixpath fixes #2033 closes #2059 --- flask/helpers.py | 22 ++++++++++++++-------- tests/test_helpers.py | 25 ++++++++++++------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 51c3b064..f37be677 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -638,18 +638,24 @@ def safe_join(directory, *pathnames): :raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed paths fall out of its boundaries. """ + + parts = [directory] + for filename in pathnames: if filename != '': filename = posixpath.normpath(filename) - for sep in _os_alt_seps: - if sep in filename: - raise NotFound() - if os.path.isabs(filename) or \ - filename == '..' or \ - filename.startswith('../'): + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == '..' + or filename.startswith('../') + ): raise NotFound() - directory = os.path.join(directory, filename) - return directory + + parts.append(filename) + + return posixpath.join(*parts) def send_from_directory(directory, filename, **options): diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 325713c0..d5706521 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -903,21 +903,20 @@ class TestStreaming(object): class TestSafeJoin(object): - def test_safe_join(self): # Valid combinations of *args and expected joined paths. passing = ( - (('a/b/c', ), 'a/b/c'), - (('/', 'a/', 'b/', 'c/', ), '/a/b/c'), - (('a', 'b', 'c', ), 'a/b/c'), - (('/a', 'b/c', ), '/a/b/c'), - (('a/b', 'X/../c'), 'a/b/c', ), - (('/a/b', 'c/X/..'), '/a/b/c', ), + (('a/b/c',), 'a/b/c'), + (('/', 'a/', 'b/', 'c/'), '/a/b/c'), + (('a', 'b', 'c'), 'a/b/c'), + (('/a', 'b/c'), '/a/b/c'), + (('a/b', 'X/../c'), 'a/b/c'), + (('/a/b', 'c/X/..'), '/a/b/c'), # If last path is '' add a slash - (('/a/b/c', '', ), '/a/b/c/', ), + (('/a/b/c', ''), '/a/b/c/'), # Preserve dot slash - (('/a/b/c', './', ), '/a/b/c/.', ), - (('a/b/c', 'X/..'), 'a/b/c/.', ), + (('/a/b/c', './'), '/a/b/c/.'), + (('a/b/c', 'X/..'), 'a/b/c/.'), # Base directory is always considered safe (('../', 'a/b/c'), '../a/b/c'), (('/..', ), '/..'), @@ -931,12 +930,12 @@ class TestSafeJoin(object): failing = ( # path.isabs and ``..'' checks ('/a', 'b', '/c'), - ('/a', '../b/c', ), + ('/a', '../b/c'), ('/a', '..', 'b/c'), # Boundaries violations after path normalization - ('/a', 'b/../b/../../c', ), + ('/a', 'b/../b/../../c'), ('/a', 'b', 'c/../..'), - ('/a', 'b/../../c', ), + ('/a', 'b/../../c'), ) for args in failing: From f7c35bf0d51d1ae02709e39fe29110e12f64fb87 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 15 May 2017 16:58:01 -0700 Subject: [PATCH 382/440] safe_join on Windows uses posixpath fixes #2033 closes #2059 --- CHANGES | 7 +++++++ flask/helpers.py | 22 ++++++++++++++-------- tests/test_helpers.py | 25 ++++++++++++------------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 613b8189..a67c8bf9 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,13 @@ Major release, unreleased method returns compressed response by default, and pretty response in debug mode. +Version 0.12.2 +-------------- + +Bugfix release + +- Fix a bug in `safe_join` on Windows. + Version 0.12.1 -------------- diff --git a/flask/helpers.py b/flask/helpers.py index c6c2cddc..4bb1d1c9 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -619,18 +619,24 @@ def safe_join(directory, *pathnames): :raises: :class:`~werkzeug.exceptions.NotFound` if one or more passed paths fall out of its boundaries. """ + + parts = [directory] + for filename in pathnames: if filename != '': filename = posixpath.normpath(filename) - for sep in _os_alt_seps: - if sep in filename: - raise NotFound() - if os.path.isabs(filename) or \ - filename == '..' or \ - filename.startswith('../'): + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == '..' + or filename.startswith('../') + ): raise NotFound() - directory = os.path.join(directory, filename) - return directory + + parts.append(filename) + + return posixpath.join(*parts) def send_from_directory(directory, filename, **options): diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b1241ce9..9320ef71 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -846,21 +846,20 @@ class TestStreaming(object): class TestSafeJoin(object): - def test_safe_join(self): # Valid combinations of *args and expected joined paths. passing = ( - (('a/b/c', ), 'a/b/c'), - (('/', 'a/', 'b/', 'c/', ), '/a/b/c'), - (('a', 'b', 'c', ), 'a/b/c'), - (('/a', 'b/c', ), '/a/b/c'), - (('a/b', 'X/../c'), 'a/b/c', ), - (('/a/b', 'c/X/..'), '/a/b/c', ), + (('a/b/c',), 'a/b/c'), + (('/', 'a/', 'b/', 'c/'), '/a/b/c'), + (('a', 'b', 'c'), 'a/b/c'), + (('/a', 'b/c'), '/a/b/c'), + (('a/b', 'X/../c'), 'a/b/c'), + (('/a/b', 'c/X/..'), '/a/b/c'), # If last path is '' add a slash - (('/a/b/c', '', ), '/a/b/c/', ), + (('/a/b/c', ''), '/a/b/c/'), # Preserve dot slash - (('/a/b/c', './', ), '/a/b/c/.', ), - (('a/b/c', 'X/..'), 'a/b/c/.', ), + (('/a/b/c', './'), '/a/b/c/.'), + (('a/b/c', 'X/..'), 'a/b/c/.'), # Base directory is always considered safe (('../', 'a/b/c'), '../a/b/c'), (('/..', ), '/..'), @@ -874,12 +873,12 @@ class TestSafeJoin(object): failing = ( # path.isabs and ``..'' checks ('/a', 'b', '/c'), - ('/a', '../b/c', ), + ('/a', '../b/c'), ('/a', '..', 'b/c'), # Boundaries violations after path normalization - ('/a', 'b/../b/../../c', ), + ('/a', 'b/../b/../../c'), ('/a', 'b', 'c/../..'), - ('/a', 'b/../../c', ), + ('/a', 'b/../../c'), ) for args in failing: From bb83ae9843fd1e170a97139d489399c4823ba779 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 16 May 2017 08:39:28 +0200 Subject: [PATCH 383/440] Release 0.12.2 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a67c8bf9..3456276a 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,7 @@ Major release, unreleased Version 0.12.2 -------------- -Bugfix release +Released on May 16 2017 - Fix a bug in `safe_join` on Windows. From 571334df8e26333f34873a3dcb84441946e6c64c Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 16 May 2017 08:39:30 +0200 Subject: [PATCH 384/440] Bump version number to 0.12.2 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 59d711b8..a9a873fa 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12.2-dev' +__version__ = '0.12.2' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From f347d3c59e3304474ca85b17e24261f127b27282 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 16 May 2017 08:40:08 +0200 Subject: [PATCH 385/440] Bump to dev version --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index a9a873fa..28a87cb6 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12.2' +__version__ = '0.12.3-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From ae133aa1734775f03560bde3d98c7f7d1c1569f7 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 20 May 2017 12:11:37 -0700 Subject: [PATCH 386/440] reorder session cookie checks to deleted, accessed, modified --- flask/sessions.py | 75 +++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 2eb887fc..182ace85 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -125,7 +125,8 @@ class SecureCookieSession(CallbackDict, SessionMixin): def on_update(self): self.modified = True self.accessed = True - CallbackDict.__init__(self, initial, on_update) + + super(SecureCookieSession, self).__init__(initial, on_update) self.modified = False self.accessed = False @@ -306,22 +307,20 @@ class SessionInterface(object): return datetime.utcnow() + app.permanent_session_lifetime def should_set_cookie(self, app, session): - """Indicates whether a cookie should be set now or not. This is - used by session backends to figure out if they should emit a - set-cookie header or not. The default behavior is controlled by - the ``SESSION_REFRESH_EACH_REQUEST`` config variable. If - it's set to ``False`` then a cookie is only set if the session is - modified, if set to ``True`` it's always set if the session is - permanent. - - This check is usually skipped if sessions get deleted. + """Used by session backends to determine if a ``Set-Cookie`` header + should be set for this session cookie for this response. If the session + has been modified, the cookie is set. If the session is permanent and + the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is + always set. + + This check is usually skipped if the session was deleted. .. versionadded:: 0.11 """ - if session.modified: - return True - save_each = app.config['SESSION_REFRESH_EACH_REQUEST'] - return save_each and session.permanent + + return session.modified or ( + session.permanent and app.config['SESSION_REFRESH_EACH_REQUEST'] + ) def open_session(self, app, request): """This method has to be implemented and must either return ``None`` @@ -387,35 +386,35 @@ class SecureCookieSessionInterface(SessionInterface): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) - if session.accessed: + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: + response.delete_cookie( + app.session_cookie_name, + domain=domain, + path=path + ) + + return + # Add a "Vary: Cookie" header if the session was accessed at all. + if session.accessed: response.headers.add('Vary', 'Cookie') - else: - - # Delete case. If there is no session we bail early. - # If the session was modified to be empty we remove the - # whole cookie. - if not session: - if session.modified: - response.delete_cookie(app.session_cookie_name, - domain=domain, path=path) - return - - # Modification case. There are upsides and downsides to - # emitting a set-cookie header each request. The behavior - # is controlled by the :meth:`should_set_cookie` method - # which performs a quick check to figure out if the cookie - # should be set or not. This is controlled by the - # SESSION_REFRESH_EACH_REQUEST config flag as well as - # the permanent flag on the session itself. - if not self.should_set_cookie(app, session): - return + if not self.should_set_cookie(app, session): + return httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) val = self.get_signing_serializer(app).dumps(dict(session)) - response.set_cookie(app.session_cookie_name, val, - expires=expires, httponly=httponly, - domain=domain, path=path, secure=secure) + response.set_cookie( + app.session_cookie_name, + val, + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure + ) From 5d9dd0b379a63d5a90265f2469f86fbd81b05853 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 20 May 2017 13:00:17 -0700 Subject: [PATCH 387/440] set session accessed for setdefault --- flask/sessions.py | 5 +++++ tests/test_basic.py | 40 ++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 182ace85..2dbd8b32 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -138,6 +138,11 @@ class SecureCookieSession(CallbackDict, SessionMixin): self.accessed = True return super(SecureCookieSession, self).get(key, default) + def setdefault(self, key, default=None): + self.accessed = True + return super(SecureCookieSession, self).setdefault(key, default) + + class NullSession(SecureCookieSession): """Class used to generate nicer error messages if sessions are not available. Will still allow read-only access to the empty session diff --git a/tests/test_basic.py b/tests/test_basic.py index dd9fd17e..54f4c8e6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -533,20 +533,22 @@ def test_session_vary_cookie(): app = flask.Flask(__name__) app.secret_key = 'testkey' - @app.route('/set-session') + @app.route('/set') def set_session(): flask.session['test'] = 'test' return '' - @app.route('/get-session') - def get_session(): - s = flask.session.get('test') - return '' + @app.route('/get') + def get(): + return flask.session.get('test') - @app.route('/get-session-with-dictionary') - def get_session_with_dictionary(): - s = flask.session['test'] - return '' + @app.route('/getitem') + def getitem(): + return flask.session['test'] + + @app.route('/setdefault') + def setdefault(): + return flask.session.setdefault('test', 'default') @app.route('/no-vary-header') def no_vary_header(): @@ -554,17 +556,19 @@ def test_session_vary_cookie(): c = app.test_client() - rv = c.get('/set-session') - assert rv.headers['Vary'] == 'Cookie' - - rv = c.get('/get-session') - assert rv.headers['Vary'] == 'Cookie' + def expect(path, header=True): + rv = c.get(path) - rv = c.get('/get-session-with-dictionary') - assert rv.headers['Vary'] == 'Cookie' + if header: + assert rv.headers['Vary'] == 'Cookie' + else: + assert 'Vary' not in rv.headers - rv = c.get('/no-vary-header') - assert 'Vary' not in rv.headers + expect('/set') + expect('/get') + expect('/getitem') + expect('/setdefault') + expect('/no-vary-header', False) def test_flashes(): From c590e820aadb9134a7fb19fe32c8ddae37199ec3 Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 11:25:02 -0700 Subject: [PATCH 388/440] Updated documentation. Replaced term development mode with debug mode. #2261 --- docs/reqcontext.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 51cd66f6..29af2677 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -119,9 +119,9 @@ understand what is actually happening. The new behavior is quite simple: not executed yet or at all (for example in test environments sometimes you might want to not execute before-request callbacks). -Now what happens on errors? In production mode if an exception is not -caught, the 500 internal server handler is called. In development mode -however the exception is not further processed and bubbles up to the WSGI +Now what happens on errors? If you are not in debug mode if an exception is not +caught, the 500 internal server handler is called. In debug mode +however the exception is not further processed and bubbles up to the WSGI server. That way things like the interactive debugger can provide helpful debug information. @@ -214,10 +214,11 @@ provide you with important information. Starting with Flask 0.7 you have finer control over that behavior by setting the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. By default it's linked to the setting of ``DEBUG``. If the application is in -debug mode the context is preserved, in production mode it's not. +debug mode the context is preserved. If debug mode is set to off, the context +is not preserved. -Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` in production mode -as it will cause your application to leak memory on exceptions. However +Do not force activate ``PRESERVE_CONTEXT_ON_EXCEPTION`` if debug mode is set to off +as it will cause your application to leak memory on exceptions. However, it can be useful during development to get the same error preserving -behavior as in development mode when attempting to debug an error that +behavior as debug mode when attempting to debug an error that only occurs under production settings. From 041c68f48baa2ee3a8437c5f0e8c6fbd6039f28f Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 11:28:58 -0700 Subject: [PATCH 389/440] Updated request context documentation. --- docs/reqcontext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 29af2677..c3d37297 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -119,7 +119,7 @@ understand what is actually happening. The new behavior is quite simple: not executed yet or at all (for example in test environments sometimes you might want to not execute before-request callbacks). -Now what happens on errors? If you are not in debug mode if an exception is not +Now what happens on errors? If you are not in debug mode and an exception is not caught, the 500 internal server handler is called. In debug mode however the exception is not further processed and bubbles up to the WSGI server. That way things like the interactive debugger can provide helpful From 9fddecd4d92763f38a42a3842abab70b0a6f4678 Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Mon, 22 May 2017 11:55:23 -0700 Subject: [PATCH 390/440] Add coverage for Blueprint.app_errorhandler This test case registers an application-wise error handler from a Blueprint. Verifies the error handler by aborting the flask app from the application itself as well as from another registered Blueprint. --- tests/test_blueprints.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index de293e7f..e48fddc0 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -91,6 +91,33 @@ def test_blueprint_specific_user_error_handling(): assert c.get('/decorator').data == b'boom' assert c.get('/function').data == b'bam' +def test_blueprint_app_error_handling(): + errors = flask.Blueprint('errors', __name__) + + @errors.app_errorhandler(403) + def forbidden_handler(e): + return 'you shall not pass', 403 + + app = flask.Flask(__name__) + + @app.route('/forbidden') + def app_forbidden(): + flask.abort(403) + + forbidden_bp = flask.Blueprint('forbidden_bp', __name__) + + @forbidden_bp.route('/nope') + def bp_forbidden(): + flask.abort(403) + + app.register_blueprint(errors) + app.register_blueprint(forbidden_bp) + + c = app.test_client() + + assert c.get('/forbidden').data == b'you shall not pass' + assert c.get('/nope').data == b'you shall not pass' + def test_blueprint_url_definitions(): bp = flask.Blueprint('test', __name__) From 409dd15c10cbf79a52af554fd26a6e9c07ccd452 Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 12:14:52 -0700 Subject: [PATCH 391/440] Added link to using Werkzeug debugger in quickstart documentation. --- docs/quickstart.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 7bdb67e4..6a18053e 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -153,6 +153,11 @@ Screenshot of the debugger in action: :class: screenshot :alt: screenshot of debugger in action +More information on using the debugger can be found in the `Werkzeug +documentation`_. + +.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/0.11/debug/#using-the-debugger + Have another debugger in mind? See :ref:`working-with-debuggers`. From 50b73f967bae6b452220155de830a2c0c24bfd2e Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 12:19:52 -0700 Subject: [PATCH 392/440] Removed the version number out of the documenation link to Werkzeug. --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 6a18053e..aea776f3 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -156,7 +156,7 @@ Screenshot of the debugger in action: More information on using the debugger can be found in the `Werkzeug documentation`_. -.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/0.11/debug/#using-the-debugger +.. _Werkzeug documentation: http://werkzeug.pocoo.org/docs/debug/#using-the-debugger Have another debugger in mind? See :ref:`working-with-debuggers`. From ced719ea18a56f6c4075e08dbe05f0e77eac1866 Mon Sep 17 00:00:00 2001 From: Hendrik Makait Date: Mon, 22 May 2017 12:30:18 -0700 Subject: [PATCH 393/440] Auto-detect create_app and make_app factory functions --- flask/cli.py | 15 +++++++++++++++ tests/test_cli.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/flask/cli.py b/flask/cli.py index 3d361be8..cdb7f094 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -46,6 +46,21 @@ def find_best_app(module): if len(matches) == 1: return matches[0] + + # Search for app factory callables. + for attr_name in 'create_app', 'make_app': + app_factory = getattr(module, attr_name, None) + if app_factory is not None and callable(app_factory): + try: + app = app_factory() + if app is not None and isinstance(app, Flask): + return app + except TypeError: + raise NoAppException('Auto-detected "%s()" in module "%s", ' + 'but could not call it without ' + 'specifying arguments.' + % (attr_name, module.__name__)) + raise NoAppException('Failed to find application in module "%s". Are ' 'you sure it contains a Flask application? Maybe ' 'you wrapped it in a WSGI middleware or you are ' diff --git a/tests/test_cli.py b/tests/test_cli.py index ab875cef..bbb6fe58 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -51,6 +51,34 @@ def test_find_best_app(test_apps): myapp = Flask('appname') assert find_best_app(Module) == Module.myapp + class Module: + @staticmethod + def create_app(): + return Flask('appname') + assert isinstance(find_best_app(Module), Flask) + assert find_best_app(Module).name == 'appname' + + class Module: + @staticmethod + def make_app(): + return Flask('appname') + assert isinstance(find_best_app(Module), Flask) + assert find_best_app(Module).name == 'appname' + + class Module: + myapp = Flask('appname1') + @staticmethod + def create_app(): + return Flask('appname2') + assert find_best_app(Module) == Module.myapp + + class Module: + myapp = Flask('appname1') + @staticmethod + def create_app(foo): + return Flask('appname2') + assert find_best_app(Module) == Module.myapp + class Module: pass pytest.raises(NoAppException, find_best_app, Module) @@ -60,6 +88,12 @@ def test_find_best_app(test_apps): myapp2 = Flask('appname2') pytest.raises(NoAppException, find_best_app, Module) + class Module: + @staticmethod + def create_app(foo): + return Flask('appname2') + pytest.raises(NoAppException, find_best_app, Module) + def test_prepare_exec_for_file(test_apps): """Expect the correct path to be set and the correct module name to be returned. From 136dbf7de014dda29f3af89d1ba4fd4f4620e25c Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Mon, 22 May 2017 13:06:47 -0700 Subject: [PATCH 394/440] Add coverage for Blueprints.(app_)context_processor Test both context_processor and app_context_processor functions. Two context parameters are added into the context: one added to the blueprint locally; another added to the app globally. The test asserts the behaviors in both blueprint scope and the app scope. The coverage for flask.blueprints is increased by 3%. --- tests/test_blueprints.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index de293e7f..cee84d4e 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -591,3 +591,45 @@ def test_add_template_test_with_name_and_template(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') assert b'Success!' in rv.data + +def test_context_processing(): + app = flask.Flask(__name__) + answer_bp = flask.Blueprint('answer_bp', __name__) + + template_string = lambda: flask.render_template_string( + '{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}' + '{% if answer %}{{ answer }} is the answer.{% endif %}' + ) + + # App global context processor + @answer_bp.app_context_processor + def not_answer_context_processor(): + return {'notanswer': 43} + + # Blueprint local context processor + @answer_bp.context_processor + def answer_context_processor(): + return {'answer': 42} + + # Setup endpoints for testing + @answer_bp.route('/bp') + def bp_page(): + return template_string() + + @app.route('/') + def app_page(): + return template_string() + + # Register the blueprint + app.register_blueprint(answer_bp) + + c = app.test_client() + + app_page_bytes = c.get('/').data + answer_page_bytes = c.get('/bp').data + + assert b'43' in app_page_bytes + assert b'42' not in app_page_bytes + + assert b'42' in answer_page_bytes + assert b'43' in answer_page_bytes From b4eb6534d52e86af3979b06734e5779f6bb9a4f6 Mon Sep 17 00:00:00 2001 From: Hendrik Makait Date: Mon, 22 May 2017 14:26:00 -0700 Subject: [PATCH 395/440] Remove unnecessary checks and reformat NoAppException messages --- flask/cli.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index cdb7f094..109ba3fe 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -37,7 +37,7 @@ def find_best_app(module): # Search for the most common names first. for attr_name in 'app', 'application': app = getattr(module, attr_name, None) - if app is not None and isinstance(app, Flask): + if isinstance(app, Flask): return app # Otherwise find the only object that is a Flask instance. @@ -50,21 +50,23 @@ def find_best_app(module): # Search for app factory callables. for attr_name in 'create_app', 'make_app': app_factory = getattr(module, attr_name, None) - if app_factory is not None and callable(app_factory): + if callable(app_factory): try: app = app_factory() - if app is not None and isinstance(app, Flask): + if isinstance(app, Flask): return app except TypeError: - raise NoAppException('Auto-detected "%s()" in module "%s", ' - 'but could not call it without ' - 'specifying arguments.' - % (attr_name, module.__name__)) - - raise NoAppException('Failed to find application in module "%s". Are ' - 'you sure it contains a Flask application? Maybe ' - 'you wrapped it in a WSGI middleware or you are ' - 'using a factory function.' % module.__name__) + raise NoAppException( + 'Auto-detected "{callable}()" in module "{module}", but ' + 'could not call it without specifying arguments.' + .format(callable=attr_name, + module=module.__name__)) + + raise NoAppException( + 'Failed to find application in module "{module}". Are you sure ' + 'it contains a Flask application? Maybe you wrapped it in a WSGI ' + 'middleware or you are using a factory function.' + .format(module=module.__name__)) def prepare_exec_for_file(filename): From 7ce01ab9b4aac9e559cd6cb2e309381cfc3cc71b Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Mon, 22 May 2017 14:14:24 -0700 Subject: [PATCH 396/440] Add coverage for Blueprint.add_app_template_global This tests the Blueprint.add_app_template_global mothod, which internally calls the Blueprint.app_template_global method. The methods are used to registering a function to the jinja template environment. This PR increases the test coverage for module flask.blueprint by 4%. --- tests/test_blueprints.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 12b58417..5f0d87da 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -660,3 +660,22 @@ def test_context_processing(): assert b'42' in answer_page_bytes assert b'43' in answer_page_bytes + +def test_template_global(): + app = flask.Flask(__name__) + bp = flask.Blueprint('bp', __name__) + @bp.app_template_global() + def get_answer(): + return 42 + # Make sure the function is not in the jinja_env already + assert 'get_answer' not in app.jinja_env.globals.keys() + app.register_blueprint(bp) + + # Tests + assert 'get_answer' in app.jinja_env.globals.keys() + assert app.jinja_env.globals['get_answer'] is get_answer + assert app.jinja_env.globals['get_answer']() == 42 + + with app.app_context(): + rv = flask.render_template_string('{{ get_answer() }}') + assert rv == '42' From d7d21f55596b141ae6ca3cc1a52e9b5be9d474eb Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 14:48:24 -0700 Subject: [PATCH 397/440] Moved WSGI-Standalone above mod_wsgi in deployment documentation. --- docs/deploying/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 6950e47a..0bc61fc2 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -32,8 +32,8 @@ Self-hosted options .. toctree:: :maxdepth: 2 - mod_wsgi wsgi-standalone + mod_wsgi uwsgi fastcgi cgi From 2b96052240ff1113bfb0c376aaf7aa376785184d Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 15:13:31 -0700 Subject: [PATCH 398/440] Moved mod_wsgi under uwsgi in TOC deployment docs. --- docs/deploying/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 0bc61fc2..da8ac14e 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -33,7 +33,7 @@ Self-hosted options :maxdepth: 2 wsgi-standalone - mod_wsgi uwsgi + mod_wsgi fastcgi cgi From 7ecdbcfa2b2d1c83c8c6561b49303ed5b1042350 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 22 May 2017 15:48:08 -0700 Subject: [PATCH 399/440] show error if multiple Flask instances are detected add changelog --- CHANGES | 3 +++ flask/cli.py | 28 +++++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 76a0d7a2..7effdfac 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,8 @@ Major release, unreleased - Allow IP address as exact session cookie domain. (`#2282`_) - ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``. (`#2282`_) +- Auto-detect 0-argument app factory called ``create_app`` or ``make_app`` from + ``FLASK_APP``. (`#2297`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1898: https://github.com/pallets/flask/pull/1898 @@ -50,6 +52,7 @@ Major release, unreleased .. _#2256: https://github.com/pallets/flask/pull/2256 .. _#2259: https://github.com/pallets/flask/pull/2259 .. _#2282: https://github.com/pallets/flask/pull/2282 +.. _#2297: https://github.com/pallets/flask/pull/2297 Version 0.12.2 -------------- diff --git a/flask/cli.py b/flask/cli.py index 109ba3fe..a10cf5e5 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -41,32 +41,42 @@ def find_best_app(module): return app # Otherwise find the only object that is a Flask instance. - matches = [v for k, v in iteritems(module.__dict__) - if isinstance(v, Flask)] + matches = [ + v for k, v in iteritems(module.__dict__) if isinstance(v, Flask) + ] if len(matches) == 1: return matches[0] + elif len(matches) > 1: + raise NoAppException( + 'Auto-detected multiple Flask applications in module "{module}".' + ' Use "FLASK_APP={module}:name" to specify the correct' + ' one.'.format(module=module.__name__) + ) # Search for app factory callables. for attr_name in 'create_app', 'make_app': app_factory = getattr(module, attr_name, None) + if callable(app_factory): try: app = app_factory() + if isinstance(app, Flask): return app except TypeError: raise NoAppException( 'Auto-detected "{callable}()" in module "{module}", but ' - 'could not call it without specifying arguments.' - .format(callable=attr_name, - module=module.__name__)) + 'could not call it without specifying arguments.'.format( + callable=attr_name, module=module.__name__ + ) + ) raise NoAppException( - 'Failed to find application in module "{module}". Are you sure ' - 'it contains a Flask application? Maybe you wrapped it in a WSGI ' - 'middleware or you are using a factory function.' - .format(module=module.__name__)) + 'Failed to find application in module "{module}". Are you sure ' + 'it contains a Flask application? Maybe you wrapped it in a WSGI ' + 'middleware.'.format(module=module.__name__) + ) def prepare_exec_for_file(filename): From 01ddf54b87850c50ee9020c6027647e0c5fca283 Mon Sep 17 00:00:00 2001 From: David Lord Date: Mon, 22 May 2017 16:12:23 -0700 Subject: [PATCH 400/440] adjust for loop style --- CHANGES | 4 ++-- flask/cli.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 7effdfac..d243d66d 100644 --- a/CHANGES +++ b/CHANGES @@ -40,8 +40,8 @@ Major release, unreleased - Allow IP address as exact session cookie domain. (`#2282`_) - ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``. (`#2282`_) -- Auto-detect 0-argument app factory called ``create_app`` or ``make_app`` from - ``FLASK_APP``. (`#2297`_) +- Auto-detect zero-argument app factory called ``create_app`` or ``make_app`` + from ``FLASK_APP``. (`#2297`_) .. _#1489: https://github.com/pallets/flask/pull/1489 .. _#1898: https://github.com/pallets/flask/pull/1898 diff --git a/flask/cli.py b/flask/cli.py index a10cf5e5..6aa66f4f 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -35,7 +35,7 @@ def find_best_app(module): from . import Flask # Search for the most common names first. - for attr_name in 'app', 'application': + for attr_name in ('app', 'application'): app = getattr(module, attr_name, None) if isinstance(app, Flask): return app @@ -55,7 +55,7 @@ def find_best_app(module): ) # Search for app factory callables. - for attr_name in 'create_app', 'make_app': + for attr_name in ('create_app', 'make_app'): app_factory = getattr(module, attr_name, None) if callable(app_factory): From 6f49089a624a13353a07db87aff5fe6f01699728 Mon Sep 17 00:00:00 2001 From: MikeTheReader Date: Mon, 22 May 2017 16:15:48 -0700 Subject: [PATCH 401/440] Added tests for make_response and get_debug_flag to improve coverage of helpers.py --- tests/test_helpers.py | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index d5706521..19c364f5 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -22,6 +22,7 @@ from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date from flask._compat import StringIO, text_type +from flask.helpers import get_debug_flag, make_response def has_encoding(name): @@ -941,3 +942,52 @@ class TestSafeJoin(object): for args in failing: with pytest.raises(NotFound): print(flask.safe_join(*args)) + +class TestHelpers(object): + + def test_get_debug_flag(self): + original_debug_value = os.environ.get('FLASK_DEBUG') or '' + os.environ['FLASK_DEBUG'] = '' + assert get_debug_flag() == None + assert get_debug_flag(default=True) == True + + os.environ['FLASK_DEBUG'] = '0' + assert get_debug_flag() == False + assert get_debug_flag(default=True) == False + + os.environ['FLASK_DEBUG'] = 'False' + assert get_debug_flag() == False + assert get_debug_flag(default=True) == False + + os.environ['FLASK_DEBUG'] = 'No' + assert get_debug_flag() == False + assert get_debug_flag(default=True) == False + + os.environ['FLASK_DEBUG'] = 'True' + assert get_debug_flag() == True + assert get_debug_flag(default=True) == True + + os.environ['FLASK_DEBUG'] = original_debug_value + + def test_make_response_no_args(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.helpers.make_response() + c = app.test_client() + rv = c.get() + assert rv + + def test_make_response_with_args(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + response = flask.helpers.make_response(flask.render_template_string('Hello World')) + response.headers['X-Parachutes'] = 'parachutes are cool' + return response + c = app.test_client() + rv = c.get() + assert rv + assert rv.headers['X-Parachutes'] == 'parachutes are cool' From a690ae27a392cfa1474e062f7655250c416cd536 Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Mon, 22 May 2017 15:49:04 -0700 Subject: [PATCH 402/440] Add coverage for Blueprint request process methods Add test to cover following methodss to the Blueprint object: before_request, after_request, before_app_request, before_app_first_request, after_app_request. This PR increases the coverage of flask.blueprints by 6%. --- tests/test_blueprints.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 5f0d87da..78da0d2c 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -679,3 +679,65 @@ def test_template_global(): with app.app_context(): rv = flask.render_template_string('{{ get_answer() }}') assert rv == '42' + +def test_request_processing(): + app = flask.Flask(__name__) + bp = flask.Blueprint('bp', __name__) + evts = [] + @bp.before_request + def before_bp(): + evts.append('before') + @bp.after_request + def after_bp(response): + response.data += b'|after' + evts.append('after') + return response + + # Setup routes for testing + @bp.route('/bp') + def bp_endpoint(): + return 'request' + + app.register_blueprint(bp) + + assert evts == [] + rv = app.test_client().get('/bp') + assert rv.data == b'request|after' + assert evts == ['before', 'after'] + +def test_app_request_processing(): + app = flask.Flask(__name__) + bp = flask.Blueprint('bp', __name__) + evts = [] + + @bp.before_app_first_request + def before_first_request(): + evts.append('first') + @bp.before_app_request + def before_app(): + evts.append('before') + @bp.after_app_request + def after_app(response): + response.data += b'|after' + evts.append('after') + return response + + app.register_blueprint(bp) + + # Setup routes for testing + @app.route('/') + def bp_endpoint(): + return 'request' + + # before first request + assert evts == [] + + # first request + resp = app.test_client().get('/').data + assert resp == b'request|after' + assert evts == ['first', 'before', 'after'] + + # second request + resp = app.test_client().get('/').data + assert resp == b'request|after' + assert evts == ['first', 'before', 'after', 'before', 'after'] From d8d712a0def83d146d155acb941c31781e7c3b3a Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Mon, 22 May 2017 16:54:52 -0700 Subject: [PATCH 403/440] Add coverage for Blueprint teardown request method Test the following methods in the Blueprint object: teardown_request, and teardown_app_request. This PR increases the coverage of blueprint module by 3%. --- tests/test_blueprints.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 78da0d2c..5c5119c0 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -692,6 +692,9 @@ def test_request_processing(): response.data += b'|after' evts.append('after') return response + @bp.teardown_request + def teardown_bp(exc): + evts.append('teardown') # Setup routes for testing @bp.route('/bp') @@ -703,7 +706,7 @@ def test_request_processing(): assert evts == [] rv = app.test_client().get('/bp') assert rv.data == b'request|after' - assert evts == ['before', 'after'] + assert evts == ['before', 'after', 'teardown'] def test_app_request_processing(): app = flask.Flask(__name__) @@ -721,6 +724,9 @@ def test_app_request_processing(): response.data += b'|after' evts.append('after') return response + @bp.teardown_app_request + def teardown_app(exc): + evts.append('teardown') app.register_blueprint(bp) @@ -735,9 +741,9 @@ def test_app_request_processing(): # first request resp = app.test_client().get('/').data assert resp == b'request|after' - assert evts == ['first', 'before', 'after'] + assert evts == ['first', 'before', 'after', 'teardown'] # second request resp = app.test_client().get('/').data assert resp == b'request|after' - assert evts == ['first', 'before', 'after', 'before', 'after'] + assert evts == ['first'] + ['before', 'after', 'teardown'] * 2 From 65fc888172fbff89a8354e8926a69b4515766389 Mon Sep 17 00:00:00 2001 From: Neil Grey Date: Mon, 22 May 2017 17:36:55 -0700 Subject: [PATCH 404/440] For Issue #2286: Replaces references to unittest in the documentation with pytest --- docs/signals.rst | 4 +- docs/testing.rst | 200 ++++++++++++++++++++++++++--------------------- flask/app.py | 2 +- 3 files changed, 112 insertions(+), 94 deletions(-) diff --git a/docs/signals.rst b/docs/signals.rst index 2426e920..40041491 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -27,7 +27,7 @@ executed in undefined order and do not modify any data. The big advantage of signals over handlers is that you can safely subscribe to them for just a split second. These temporary -subscriptions are helpful for unittesting for example. Say you want to +subscriptions are helpful for unit testing for example. Say you want to know what templates were rendered as part of a request: signals allow you to do exactly that. @@ -45,7 +45,7 @@ signal. When you subscribe to a signal, be sure to also provide a sender unless you really want to listen for signals from all applications. This is especially true if you are developing an extension. -For example, here is a helper context manager that can be used in a unittest +For example, here is a helper context manager that can be used in a unit test to determine which templates were rendered and what variables were passed to the template:: diff --git a/docs/testing.rst b/docs/testing.rst index bd1c8467..eb0737da 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -5,23 +5,30 @@ Testing Flask Applications **Something that is untested is broken.** -The origin of this quote is unknown and while it is not entirely correct, it is also -not far from the truth. Untested applications make it hard to +The origin of this quote is unknown and while it is not entirely correct, it +is also not far from the truth. Untested applications make it hard to improve existing code and developers of untested applications tend to become pretty paranoid. If an application has automated tests, you can safely make changes and instantly know if anything breaks. Flask provides a way to test your application by exposing the Werkzeug test :class:`~werkzeug.test.Client` and handling the context locals for you. -You can then use that with your favourite testing solution. In this documentation -we will use the :mod:`unittest` package that comes pre-installed with Python. +You can then use that with your favourite testing solution. + +In this documentation we will use the `pytest`_ package as the base +framework for our tests. You can install it with ``pip``, like so:: + + pip install pytest + +.. _pytest: + https://pytest.org The Application --------------- First, we need an application to test; we will use the application from the :ref:`tutorial`. If you don't have that application yet, get the -sources from `the examples`_. +source code from `the examples`_. .. _the examples: https://github.com/pallets/flask/tree/master/examples/flaskr/ @@ -29,92 +36,89 @@ sources from `the examples`_. The Testing Skeleton -------------------- -In order to test the application, we add a second module -(:file:`flaskr_tests.py`) and create a unittest skeleton there:: +We begin by adding a tests directory under the application root. Then +create a Python file to store our tests (:file:`test_flaskr.py`). When we +format the filename like ``test_*.py``, it will be auto-discoverable by +pytest. + +Next, we create a `pytest fixture`_ called +:func:`client` that configures +the application for testing and initializes a new database.:: import os - from flaskr import flaskr - import unittest import tempfile + import pytest + from flaskr import flaskr - class FlaskrTestCase(unittest.TestCase): - - def setUp(self): - self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.testing = True - self.app = flaskr.app.test_client() - with flaskr.app.app_context(): - flaskr.init_db() + @pytest.fixture + def client(request): + db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + flaskr.app.config['TESTING'] = True + client = flaskr.app.test_client() + with flaskr.app.app_context(): + flaskr.init_db() - def tearDown(self): - os.close(self.db_fd) + def teardown(): + os.close(db_fd) os.unlink(flaskr.app.config['DATABASE']) + request.addfinalizer(teardown) - if __name__ == '__main__': - unittest.main() + return client -The code in the :meth:`~unittest.TestCase.setUp` method creates a new test -client and initializes a new database. This function is called before -each individual test function is run. To delete the database after the -test, we close the file and remove it from the filesystem in the -:meth:`~unittest.TestCase.tearDown` method. Additionally during setup the -``TESTING`` config flag is activated. What it does is disable the error -catching during request handling so that you get better error reports when -performing test requests against the application. +This client fixture will be called by each individual test. It gives us a +simple interface to the application, where we can trigger test requests to the +application. The client will also keep track of cookies for us. -This test client will give us a simple interface to the application. We can -trigger test requests to the application, and the client will also keep track -of cookies for us. +During setup, the ``TESTING`` config flag is activated. What +this does is disable error catching during request handling, so that +you get better error reports when performing test requests against the +application. -Because SQLite3 is filesystem-based we can easily use the tempfile module +Because SQLite3 is filesystem-based, we can easily use the :mod:`tempfile` module to create a temporary database and initialize it. The :func:`~tempfile.mkstemp` function does two things for us: it returns a low-level file handle and a random file name, the latter we use as database name. We just have to keep the `db_fd` around so that we can use the :func:`os.close` function to close the file. +To delete the database after the test, we close the file and remove it +from the filesystem in the +:func:`teardown` function. + If we now run the test suite, we should see the following output:: - $ python flaskr_tests.py + $ pytest - ---------------------------------------------------------------------- - Ran 0 tests in 0.000s + ================ test session starts ================ + rootdir: ./flask/examples/flaskr, inifile: setup.cfg + collected 0 items - OK + =========== no tests ran in 0.07 seconds ============ -Even though it did not run any actual tests, we already know that our flaskr +Even though it did not run any actual tests, we already know that our ``flaskr`` application is syntactically valid, otherwise the import would have died with an exception. +.. _pytest fixture: + https://docs.pytest.org/en/latest/fixture.html + The First Test -------------- Now it's time to start testing the functionality of the application. Let's check that the application shows "No entries here so far" if we -access the root of the application (``/``). To do this, we add a new -test method to our class, like this:: +access the root of the application (``/``). To do this, we add a new +test function to :file:`test_flaskr.py`, like this:: - class FlaskrTestCase(unittest.TestCase): - - def setUp(self): - self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.testing = True - self.app = flaskr.app.test_client() - with flaskr.app.app_context(): - flaskr.init_db() - - def tearDown(self): - os.close(self.db_fd) - os.unlink(flaskr.app.config['DATABASE']) - - def test_empty_db(self): - rv = self.app.get('/') - assert b'No entries here so far' in rv.data + def test_empty_db(client): + """Start with a blank database.""" + rv = client.get('/') + assert b'No entries here so far' in rv.data Notice that our test functions begin with the word `test`; this allows -:mod:`unittest` to automatically identify the method as a test to run. +`pytest`_ to automatically identify the function as a test to run. -By using `self.app.get` we can send an HTTP ``GET`` request to the application with +By using `client.get` we can send an HTTP ``GET`` request to the application with the given path. The return value will be a :class:`~flask.Flask.response_class` object. We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect the return value (as string) from the application. In this case, we ensure that @@ -122,12 +126,15 @@ the return value (as string) from the application. In this case, we ensure that Run it again and you should see one passing test:: - $ python flaskr_tests.py - . - ---------------------------------------------------------------------- - Ran 1 test in 0.034s + $ pytest -v + + ================ test session starts ================ + rootdir: ./flask/examples/flaskr, inifile: setup.cfg + collected 1 items - OK + tests/test_flaskr.py::test_empty_db PASSED + + ============= 1 passed in 0.10 seconds ============== Logging In and Out ------------------ @@ -138,39 +145,45 @@ of the application. To do this, we fire some requests to the login and logout pages with the required form data (username and password). And because the login and logout pages redirect, we tell the client to `follow_redirects`. -Add the following two methods to your `FlaskrTestCase` class:: +Add the following two functions to your :file:`test_flaskr.py` file:: - def login(self, username, password): - return self.app.post('/login', data=dict( - username=username, - password=password - ), follow_redirects=True) + def login(client, username, password): + return client.post('/login', data=dict( + username=username, + password=password + ), follow_redirects=True) - def logout(self): - return self.app.get('/logout', follow_redirects=True) + def logout(client): + return client.get('/logout', follow_redirects=True) Now we can easily test that logging in and out works and that it fails with -invalid credentials. Add this new test to the class:: - - def test_login_logout(self): - rv = self.login('admin', 'default') - assert b'You were logged in' in rv.data - rv = self.logout() - assert b'You were logged out' in rv.data - rv = self.login('adminx', 'default') - assert b'Invalid username' in rv.data - rv = self.login('admin', 'defaultx') - assert b'Invalid password' in rv.data +invalid credentials. Add this new test function:: + + def test_login_logout(client): + """Make sure login and logout works""" + rv = login(client, flaskr.app.config['USERNAME'], + flaskr.app.config['PASSWORD']) + assert b'You were logged in' in rv.data + rv = logout(client) + assert b'You were logged out' in rv.data + rv = login(client, flaskr.app.config['USERNAME'] + 'x', + flaskr.app.config['PASSWORD']) + assert b'Invalid username' in rv.data + rv = login(client, flaskr.app.config['USERNAME'], + flaskr.app.config['PASSWORD'] + 'x') + assert b'Invalid password' in rv.data Test Adding Messages -------------------- -We should also test that adding messages works. Add a new test method +We should also test that adding messages works. Add a new test function like this:: - def test_messages(self): - self.login('admin', 'default') - rv = self.app.post('/add', data=dict( + def test_messages(client): + """Test that messages work""" + login(client, flaskr.app.config['USERNAME'], + flaskr.app.config['PASSWORD']) + rv = client.post('/add', data=dict( title='', text='HTML allowed here' ), follow_redirects=True) @@ -183,12 +196,17 @@ which is the intended behavior. Running that should now give us three passing tests:: - $ python flaskr_tests.py - ... - ---------------------------------------------------------------------- - Ran 3 tests in 0.332s + $ pytest -v + + ================ test session starts ================ + rootdir: ./flask/examples/flaskr, inifile: setup.cfg + collected 3 items + + tests/test_flaskr.py::test_empty_db PASSED + tests/test_flaskr.py::test_login_logout PASSED + tests/test_flaskr.py::test_messages PASSED - OK + ============= 3 passed in 0.23 seconds ============== For more complex tests with headers and status codes, check out the `MiniTwit Example`_ from the sources which contains a larger test diff --git a/flask/app.py b/flask/app.py index 703cfef2..d851bf83 100644 --- a/flask/app.py +++ b/flask/app.py @@ -222,7 +222,7 @@ class Flask(_PackageBoundObject): #: 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 unittest helpers that have an + #: For example this might activate test helpers that have an #: additional runtime cost which should not be enabled by default. #: #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the From 4e9d51b39b674b5c1c00081c1b457ff03ba6e76c Mon Sep 17 00:00:00 2001 From: Neil Grey Date: Mon, 22 May 2017 17:42:30 -0700 Subject: [PATCH 405/440] For Issue #2286: Fixing indenting of test_login_logout --- docs/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index eb0737da..7a16e336 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -159,7 +159,7 @@ Add the following two functions to your :file:`test_flaskr.py` file:: Now we can easily test that logging in and out works and that it fails with invalid credentials. Add this new test function:: - def test_login_logout(client): + def test_login_logout(client): """Make sure login and logout works""" rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD']) From 65b61aa7c202bb26df53e2cb271269fddd95aeee Mon Sep 17 00:00:00 2001 From: Tully Rankin Date: Mon, 22 May 2017 18:08:40 -0700 Subject: [PATCH 406/440] Added uWSGI and example usage to stand-alone WSGI containers documentation (#2302) --- docs/deploying/wsgi-standalone.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index ad43c144..bf680976 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -27,6 +27,22 @@ For example, to run a Flask application with 4 worker processes (``-w .. _eventlet: http://eventlet.net/ .. _greenlet: https://greenlet.readthedocs.io/en/latest/ +uWSGI +-------- + +`uWSGI`_ is a fast application server written in C. It is very configurable +which makes it more complicated to setup than gunicorn. + +Running `uWSGI HTTP Router`_:: + + uwsgi --http 127.0.0.1:5000 --module myproject:app + +For a more optimized setup, see `configuring uWSGI and NGINX`_. + +.. _uWSGI: http://uwsgi-docs.readthedocs.io/en/latest/ +.. _uWSGI HTTP Router: http://uwsgi-docs.readthedocs.io/en/latest/HTTP.html#the-uwsgi-http-https-router +.. _configuring uWSGI and NGINX: uwsgi.html#starting-your-app-with-uwsgi + Gevent ------- From 378a11f99275a6b80d365a5d51d469844bf9ca17 Mon Sep 17 00:00:00 2001 From: Neil Grey Date: Mon, 22 May 2017 18:22:08 -0700 Subject: [PATCH 407/440] For Issue #2286: Updating test_flaskr to use yield inside fixture --- docs/testing.rst | 15 +++++---------- examples/flaskr/tests/test_flaskr.py | 10 +++------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 7a16e336..67b8aaea 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -57,13 +57,9 @@ the application for testing and initializes a new database.:: client = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() - - def teardown(): - os.close(db_fd) - os.unlink(flaskr.app.config['DATABASE']) - request.addfinalizer(teardown) - - return client + yield client + os.close(db_fd) + os.unlink(flaskr.app.config['DATABASE']) This client fixture will be called by each individual test. It gives us a simple interface to the application, where we can trigger test requests to the @@ -82,8 +78,7 @@ database name. We just have to keep the `db_fd` around so that we can use the :func:`os.close` function to close the file. To delete the database after the test, we close the file and remove it -from the filesystem in the -:func:`teardown` function. +from the filesystem. If we now run the test suite, we should see the following output:: @@ -118,7 +113,7 @@ test function to :file:`test_flaskr.py`, like this:: Notice that our test functions begin with the word `test`; this allows `pytest`_ to automatically identify the function as a test to run. -By using `client.get` we can send an HTTP ``GET`` request to the application with +By using ``client.get`` we can send an HTTP ``GET`` request to the application with the given path. The return value will be a :class:`~flask.Flask.response_class` object. We can now use the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute to inspect the return value (as string) from the application. In this case, we ensure that diff --git a/examples/flaskr/tests/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py index 663e92e0..df32cd4b 100644 --- a/examples/flaskr/tests/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -22,13 +22,9 @@ def client(request): client = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() - - def teardown(): - os.close(db_fd) - os.unlink(flaskr.app.config['DATABASE']) - request.addfinalizer(teardown) - - return client + yield client + os.close(db_fd) + os.unlink(flaskr.app.config['DATABASE']) def login(client, username, password): From fd4a363657f3b4f13611e029c52fcb8c806f7229 Mon Sep 17 00:00:00 2001 From: MikeTheReader Date: Mon, 22 May 2017 20:49:37 -0700 Subject: [PATCH 408/440] Modifications based on review --- tests/test_helpers.py | 44 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 19c364f5..a5863da9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -945,49 +945,35 @@ class TestSafeJoin(object): class TestHelpers(object): - def test_get_debug_flag(self): - original_debug_value = os.environ.get('FLASK_DEBUG') or '' - os.environ['FLASK_DEBUG'] = '' + def test_get_debug_flag(self, monkeypatch): + monkeypatch.setenv('FLASK_DEBUG', '') assert get_debug_flag() == None assert get_debug_flag(default=True) == True - os.environ['FLASK_DEBUG'] = '0' + monkeypatch.setenv('FLASK_DEBUG', '0') assert get_debug_flag() == False assert get_debug_flag(default=True) == False - os.environ['FLASK_DEBUG'] = 'False' + monkeypatch.setenv('FLASK_DEBUG', 'False') assert get_debug_flag() == False assert get_debug_flag(default=True) == False - os.environ['FLASK_DEBUG'] = 'No' + monkeypatch.setenv('FLASK_DEBUG', 'No') assert get_debug_flag() == False assert get_debug_flag(default=True) == False - os.environ['FLASK_DEBUG'] = 'True' + monkeypatch.setenv('FLASK_DEBUG', 'True') assert get_debug_flag() == True assert get_debug_flag(default=True) == True - os.environ['FLASK_DEBUG'] = original_debug_value - - def test_make_response_no_args(self): + def test_make_response(self): app = flask.Flask(__name__) - app.testing = True - @app.route('/') - def index(): - return flask.helpers.make_response() - c = app.test_client() - rv = c.get() - assert rv + with app.test_request_context(): + rv = flask.helpers.make_response() + assert rv.status_code == 200 + assert rv.mimetype == 'text/html' - def test_make_response_with_args(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/') - def index(): - response = flask.helpers.make_response(flask.render_template_string('Hello World')) - response.headers['X-Parachutes'] = 'parachutes are cool' - return response - c = app.test_client() - rv = c.get() - assert rv - assert rv.headers['X-Parachutes'] == 'parachutes are cool' + rv = flask.helpers.make_response('Hello') + assert rv.status_code == 200 + assert rv.data == b'Hello' + assert rv.mimetype == 'text/html' From 11d2eec3acb7d0cf291b572e3b6b493df5fadc6b Mon Sep 17 00:00:00 2001 From: Andrey Kislyuk Date: Mon, 22 May 2017 23:46:22 -0700 Subject: [PATCH 409/440] Fix refactoring error in static_folder docstring (#2310) --- flask/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 703cfef2..13a56c68 100644 --- a/flask/app.py +++ b/flask/app.py @@ -133,8 +133,6 @@ 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. - folder in the root path of the application. Defaults - to None. :param host_matching: sets the app's ``url_map.host_matching`` to the given given value. Defaults to False. :param static_host: the host to use when adding the static route. Defaults From cd412b20dc64f3bfa980c9993e5a4dce39a22b94 Mon Sep 17 00:00:00 2001 From: MikeTheReader Date: Tue, 23 May 2017 07:51:57 -0700 Subject: [PATCH 410/440] Parameterize test_get_debug_flag --- tests/test_helpers.py | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index a5863da9..1f623559 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -945,26 +945,20 @@ class TestSafeJoin(object): class TestHelpers(object): - def test_get_debug_flag(self, monkeypatch): - monkeypatch.setenv('FLASK_DEBUG', '') - assert get_debug_flag() == None - assert get_debug_flag(default=True) == True - - monkeypatch.setenv('FLASK_DEBUG', '0') - assert get_debug_flag() == False - assert get_debug_flag(default=True) == False - - monkeypatch.setenv('FLASK_DEBUG', 'False') - assert get_debug_flag() == False - assert get_debug_flag(default=True) == False - - monkeypatch.setenv('FLASK_DEBUG', 'No') - assert get_debug_flag() == False - assert get_debug_flag(default=True) == False - - monkeypatch.setenv('FLASK_DEBUG', 'True') - assert get_debug_flag() == True - assert get_debug_flag(default=True) == True + @pytest.mark.parametrize("debug, expected_flag, expected_default_flag", [ + ('', None, True), + ('0', False, False), + ('False', False, False), + ('No', False, False), + ('True', True, True) + ]) + def test_get_debug_flag(self, monkeypatch, debug, expected_flag, expected_default_flag): + monkeypatch.setenv('FLASK_DEBUG', debug) + if expected_flag is None: + assert get_debug_flag() is None + else: + assert get_debug_flag() == expected_flag + assert get_debug_flag(default=True) == expected_default_flag def test_make_response(self): app = flask.Flask(__name__) From 7c882a457b734cd0a38d41c7e930e375a6ce6e4c Mon Sep 17 00:00:00 2001 From: MikeTheReader Date: Tue, 23 May 2017 07:59:53 -0700 Subject: [PATCH 411/440] Replace double quotes with single quotes --- tests/test_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 1f623559..5af95f18 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -945,7 +945,7 @@ class TestSafeJoin(object): class TestHelpers(object): - @pytest.mark.parametrize("debug, expected_flag, expected_default_flag", [ + @pytest.mark.parametrize('debug, expected_flag, expected_default_flag', [ ('', None, True), ('0', False, False), ('False', False, False), From 2339f1f24e29adfd263cb6ebcb98c09ecb4396d9 Mon Sep 17 00:00:00 2001 From: Christopher Sorenson Date: Mon, 22 May 2017 18:39:07 -0500 Subject: [PATCH 412/440] making some updates to the CONTRIBUTING.rst --- CONTRIBUTING.rst | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 852e44be..80b6af35 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -24,12 +24,31 @@ Reporting issues Submitting patches ================== + +- `Make sure you have a github account `_ +- `Download and install latest version of git on your + machine `_ +- `Set up your username in git + `_ +- `Set up your email in git + `_ +- Fork flask to your github account (Click the Fork button) +- `Copy your github fork locally + `_ +- Create a branch to identify the issue you would like to work + on (eg 2287-dry-test-suite) +- Using your favorite editor, make your changes, + `committing as you go `_ - Include tests if your patch is supposed to solve a bug, and explain clearly under which circumstances the bug happens. Make sure the test fails without your patch. - - Try to follow `PEP8 `_, but you may ignore the line-length-limit if following it would make the code uglier. +- `Run tests. + `_ + When tests pass push changes to github and `create pull request + `_ +- Celebrate 🎉 Running the testsuite From 5963cb5a5172bce3a1ff7b0f8c64230f043c9cfd Mon Sep 17 00:00:00 2001 From: bovarysme Date: Tue, 23 May 2017 18:21:29 +0200 Subject: [PATCH 413/440] Use the yield syntax in pytest's fixtures --- examples/minitwit/tests/test_minitwit.py | 12 +++++------- tests/conftest.py | 19 ++++++++++--------- tests/test_ext.py | 22 ++++++++++------------ 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/examples/minitwit/tests/test_minitwit.py b/examples/minitwit/tests/test_minitwit.py index 50ca26d9..c8992e57 100644 --- a/examples/minitwit/tests/test_minitwit.py +++ b/examples/minitwit/tests/test_minitwit.py @@ -15,18 +15,16 @@ from minitwit import minitwit @pytest.fixture -def client(request): +def client(): db_fd, minitwit.app.config['DATABASE'] = tempfile.mkstemp() client = minitwit.app.test_client() with minitwit.app.app_context(): minitwit.init_db() - def teardown(): - """Get rid of the database again after each test.""" - os.close(db_fd) - os.unlink(minitwit.app.config['DATABASE']) - request.addfinalizer(teardown) - return client + yield client + + os.close(db_fd) + os.unlink(minitwit.app.config['DATABASE']) def register(client, username, password, password2=None, email=None): diff --git a/tests/conftest.py b/tests/conftest.py index 8c9541de..eb130db6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,16 +22,17 @@ def test_apps(monkeypatch): os.path.dirname(__file__), 'test_apps')) ) + @pytest.fixture(autouse=True) -def leak_detector(request): - def ensure_clean_request_context(): - # make sure we're not leaking a request context since we are - # testing flask internally in debug mode in a few cases - leaks = [] - while flask._request_ctx_stack.top is not None: - leaks.append(flask._request_ctx_stack.pop()) - assert leaks == [] - request.addfinalizer(ensure_clean_request_context) +def leak_detector(): + yield + + # make sure we're not leaking a request context since we are + # testing flask internally in debug mode in a few cases + leaks = [] + while flask._request_ctx_stack.top is not None: + leaks.append(flask._request_ctx_stack.pop()) + assert leaks == [] @pytest.fixture(params=(True, False)) diff --git a/tests/test_ext.py b/tests/test_ext.py index ebb5f02d..48214905 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -21,19 +21,18 @@ from flask._compat import PY2 @pytest.fixture(autouse=True) -def disable_extwarnings(request, recwarn): +def disable_extwarnings(recwarn): from flask.exthook import ExtDeprecationWarning - def inner(): - assert set(w.category for w in recwarn.list) \ - <= set([ExtDeprecationWarning]) - recwarn.clear() + yield - request.addfinalizer(inner) + assert set(w.category for w in recwarn.list) \ + <= set([ExtDeprecationWarning]) + recwarn.clear() @pytest.fixture(autouse=True) -def importhook_setup(monkeypatch, request): +def importhook_setup(monkeypatch): # we clear this out for various reasons. The most important one is # that a real flaskext could be in there which would disable our # fake package. Secondly we want to make sure that the flaskext @@ -58,12 +57,11 @@ def importhook_setup(monkeypatch, request): import_hooks += 1 assert import_hooks == 1 - def teardown(): - from flask import ext - for key in ext.__dict__: - assert '.' not in key + yield - request.addfinalizer(teardown) + from flask import ext + for key in ext.__dict__: + assert '.' not in key @pytest.fixture From 668061a5fc928a5055815acf818b02baf1aef37b Mon Sep 17 00:00:00 2001 From: Florian Sachs Date: Sun, 8 Jan 2017 20:42:41 +0100 Subject: [PATCH 414/440] Register errorhandlers for Exceptions Allow a default errorhandler by registering an errorhandler for HTTPException tests included --- flask/app.py | 11 ++++++++++- tests/test_user_error_handler.py | 25 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 13a56c68..1251d2fb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1484,7 +1484,16 @@ class Flask(_PackageBoundObject): return handler # fall back to app handlers - return find_handler(self.error_handler_spec[None].get(code)) + handler = find_handler(self.error_handler_spec[None].get(code)) + if handler is not None: + return handler + + try: + handler = find_handler(self.error_handler_spec[None][None]) + except KeyError: + handler = None + + return handler def handle_http_exception(self, e): """Handles an HTTP exception. By default this will invoke the diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index 11677433..b6d4ca34 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from werkzeug.exceptions import Forbidden, InternalServerError +from werkzeug.exceptions import Forbidden, InternalServerError, HTTPException, NotFound import flask @@ -32,6 +32,29 @@ def test_error_handler_no_match(): assert c.get('/keyerror').data == b'KeyError' +def test_default_error_handler(): + app = flask.Flask(__name__) + + @app.errorhandler(HTTPException) + def catchall_errorhandler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'default' + + @app.errorhandler(Forbidden) + def catchall_errorhandler(e): + assert isinstance(e, Forbidden) + return 'forbidden' + + @app.route('/forbidden') + def forbidden(): + raise Forbidden() + + c = app.test_client() + assert c.get('/undefined').data == b'default' + assert c.get('/forbidden').data == b'forbidden' + + def test_error_handler_subclass(): app = flask.Flask(__name__) From 637eae548935ad1434459c1afac286c8686abeed Mon Sep 17 00:00:00 2001 From: cerickson Date: Mon, 22 May 2017 15:57:31 -0700 Subject: [PATCH 415/440] Added support for generic HTTPException handlers on app and blueprints Error handlers are now returned in order of blueprint:code, app:code, blueprint:HTTPException, app:HTTPException, None Corresponding tests also added. Ref issue #941, pr #1383, #2082, #2144 --- flask/app.py | 33 ++++++------- tests/test_user_error_handler.py | 80 ++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/flask/app.py b/flask/app.py index 1251d2fb..f4c73978 100644 --- a/flask/app.py +++ b/flask/app.py @@ -133,6 +133,8 @@ 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. + folder in the root path of the application. Defaults + to None. :param host_matching: sets the app's ``url_map.host_matching`` to the given given value. Defaults to False. :param static_host: the host to use when adding the static route. Defaults @@ -1460,15 +1462,17 @@ class Flask(_PackageBoundObject): return f def _find_error_handler(self, e): - """Finds a registered error handler for the request’s blueprint. - Otherwise falls back to the app, returns None if not a suitable - handler is found. + """Find a registered error handler for a request in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint generic HTTPException handler, app generic HTTPException handler, + and returns None if a suitable handler is not found. """ exc_class, code = self._get_exc_class_and_code(type(e)) def find_handler(handler_map): if not handler_map: return + for cls in exc_class.__mro__: handler = handler_map.get(cls) if handler is not None: @@ -1476,24 +1480,13 @@ class Flask(_PackageBoundObject): handler_map[exc_class] = handler return handler - # try blueprint handlers - handler = find_handler(self.error_handler_spec - .get(request.blueprint, {}) - .get(code)) - if handler is not None: - return handler - - # fall back to app handlers - handler = find_handler(self.error_handler_spec[None].get(code)) - if handler is not None: - return handler - - try: - handler = find_handler(self.error_handler_spec[None][None]) - except KeyError: - handler = None + # check for any in blueprint or app + for name, c in ((request.blueprint, code), (None, code), + (request.blueprint, None), (None, None)): + handler = find_handler(self.error_handler_spec.get(name, {}).get(c)) - return handler + if handler: + return handler def handle_http_exception(self, e): """Handles an HTTP exception. By default this will invoke the diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index b6d4ca34..24ffe73f 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- -from werkzeug.exceptions import Forbidden, InternalServerError, HTTPException, NotFound +from werkzeug.exceptions import ( + Forbidden, + InternalServerError, + HTTPException, + NotFound + ) import flask @@ -32,29 +37,6 @@ def test_error_handler_no_match(): assert c.get('/keyerror').data == b'KeyError' -def test_default_error_handler(): - app = flask.Flask(__name__) - - @app.errorhandler(HTTPException) - def catchall_errorhandler(e): - assert isinstance(e, HTTPException) - assert isinstance(e, NotFound) - return 'default' - - @app.errorhandler(Forbidden) - def catchall_errorhandler(e): - assert isinstance(e, Forbidden) - return 'forbidden' - - @app.route('/forbidden') - def forbidden(): - raise Forbidden() - - c = app.test_client() - assert c.get('/undefined').data == b'default' - assert c.get('/forbidden').data == b'forbidden' - - def test_error_handler_subclass(): app = flask.Flask(__name__) @@ -161,3 +143,53 @@ def test_error_handler_blueprint(): assert c.get('/error').data == b'app-error' assert c.get('/bp/error').data == b'bp-error' + + +def test_default_error_handler(): + bp = flask.Blueprint('bp', __name__) + + @bp.errorhandler(HTTPException) + def bp_exception_handler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'bp-default' + + @bp.errorhandler(Forbidden) + def bp_exception_handler(e): + assert isinstance(e, Forbidden) + return 'bp-forbidden' + + @bp.route('/undefined') + def bp_registered_test(): + raise NotFound() + + @bp.route('/forbidden') + def bp_forbidden_test(): + raise Forbidden() + + app = flask.Flask(__name__) + + @app.errorhandler(HTTPException) + def catchall_errorhandler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'default' + + @app.errorhandler(Forbidden) + def catchall_errorhandler(e): + assert isinstance(e, Forbidden) + return 'forbidden' + + @app.route('/forbidden') + def forbidden(): + raise Forbidden() + + app.register_blueprint(bp, url_prefix='/bp') + + c = app.test_client() + assert c.get('/bp/undefined').data == b'bp-default' + assert c.get('/bp/forbidden').data == b'bp-forbidden' + assert c.get('/undefined').data == b'default' + assert c.get('/forbidden').data == b'forbidden' + + From 4f815015b803a146ddc24a2dfadd9ed9b22d7de7 Mon Sep 17 00:00:00 2001 From: cerickson Date: Mon, 22 May 2017 15:57:31 -0700 Subject: [PATCH 416/440] Added support for generic HTTPException handlers on app and blueprints Error handlers are now returned in order of blueprint:code, app:code, blueprint:HTTPException, app:HTTPException, None Corresponding tests also added. Ref issue #941, pr #1383, #2082, #2144 --- flask/app.py | 33 ++++++------- tests/test_user_error_handler.py | 80 ++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/flask/app.py b/flask/app.py index 1251d2fb..f4c73978 100644 --- a/flask/app.py +++ b/flask/app.py @@ -133,6 +133,8 @@ 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. + folder in the root path of the application. Defaults + to None. :param host_matching: sets the app's ``url_map.host_matching`` to the given given value. Defaults to False. :param static_host: the host to use when adding the static route. Defaults @@ -1460,15 +1462,17 @@ class Flask(_PackageBoundObject): return f def _find_error_handler(self, e): - """Finds a registered error handler for the request’s blueprint. - Otherwise falls back to the app, returns None if not a suitable - handler is found. + """Find a registered error handler for a request in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint generic HTTPException handler, app generic HTTPException handler, + and returns None if a suitable handler is not found. """ exc_class, code = self._get_exc_class_and_code(type(e)) def find_handler(handler_map): if not handler_map: return + for cls in exc_class.__mro__: handler = handler_map.get(cls) if handler is not None: @@ -1476,24 +1480,13 @@ class Flask(_PackageBoundObject): handler_map[exc_class] = handler return handler - # try blueprint handlers - handler = find_handler(self.error_handler_spec - .get(request.blueprint, {}) - .get(code)) - if handler is not None: - return handler - - # fall back to app handlers - handler = find_handler(self.error_handler_spec[None].get(code)) - if handler is not None: - return handler - - try: - handler = find_handler(self.error_handler_spec[None][None]) - except KeyError: - handler = None + # check for any in blueprint or app + for name, c in ((request.blueprint, code), (None, code), + (request.blueprint, None), (None, None)): + handler = find_handler(self.error_handler_spec.get(name, {}).get(c)) - return handler + if handler: + return handler def handle_http_exception(self, e): """Handles an HTTP exception. By default this will invoke the diff --git a/tests/test_user_error_handler.py b/tests/test_user_error_handler.py index b6d4ca34..24ffe73f 100644 --- a/tests/test_user_error_handler.py +++ b/tests/test_user_error_handler.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- -from werkzeug.exceptions import Forbidden, InternalServerError, HTTPException, NotFound +from werkzeug.exceptions import ( + Forbidden, + InternalServerError, + HTTPException, + NotFound + ) import flask @@ -32,29 +37,6 @@ def test_error_handler_no_match(): assert c.get('/keyerror').data == b'KeyError' -def test_default_error_handler(): - app = flask.Flask(__name__) - - @app.errorhandler(HTTPException) - def catchall_errorhandler(e): - assert isinstance(e, HTTPException) - assert isinstance(e, NotFound) - return 'default' - - @app.errorhandler(Forbidden) - def catchall_errorhandler(e): - assert isinstance(e, Forbidden) - return 'forbidden' - - @app.route('/forbidden') - def forbidden(): - raise Forbidden() - - c = app.test_client() - assert c.get('/undefined').data == b'default' - assert c.get('/forbidden').data == b'forbidden' - - def test_error_handler_subclass(): app = flask.Flask(__name__) @@ -161,3 +143,53 @@ def test_error_handler_blueprint(): assert c.get('/error').data == b'app-error' assert c.get('/bp/error').data == b'bp-error' + + +def test_default_error_handler(): + bp = flask.Blueprint('bp', __name__) + + @bp.errorhandler(HTTPException) + def bp_exception_handler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'bp-default' + + @bp.errorhandler(Forbidden) + def bp_exception_handler(e): + assert isinstance(e, Forbidden) + return 'bp-forbidden' + + @bp.route('/undefined') + def bp_registered_test(): + raise NotFound() + + @bp.route('/forbidden') + def bp_forbidden_test(): + raise Forbidden() + + app = flask.Flask(__name__) + + @app.errorhandler(HTTPException) + def catchall_errorhandler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return 'default' + + @app.errorhandler(Forbidden) + def catchall_errorhandler(e): + assert isinstance(e, Forbidden) + return 'forbidden' + + @app.route('/forbidden') + def forbidden(): + raise Forbidden() + + app.register_blueprint(bp, url_prefix='/bp') + + c = app.test_client() + assert c.get('/bp/undefined').data == b'bp-default' + assert c.get('/bp/forbidden').data == b'bp-forbidden' + assert c.get('/undefined').data == b'default' + assert c.get('/forbidden').data == b'forbidden' + + From 361dba7e3af06be8f81c7bdd5067c59e08df2df9 Mon Sep 17 00:00:00 2001 From: cerickson Date: Tue, 23 May 2017 10:49:01 -0700 Subject: [PATCH 417/440] removed dupe text from merge --- flask/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index f4c73978..6e45774e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -133,8 +133,6 @@ 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. - folder in the root path of the application. Defaults - to None. :param host_matching: sets the app's ``url_map.host_matching`` to the given given value. Defaults to False. :param static_host: the host to use when adding the static route. Defaults From 532ca2e08961b6ffd925c28581c45795986755ee Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 23 May 2017 11:18:48 -0700 Subject: [PATCH 418/440] reorganize git instructions --- CONTRIBUTING.rst | 58 ++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 80b6af35..22828608 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -24,32 +24,42 @@ Reporting issues Submitting patches ================== - -- `Make sure you have a github account `_ -- `Download and install latest version of git on your - machine `_ -- `Set up your username in git - `_ -- `Set up your email in git - `_ -- Fork flask to your github account (Click the Fork button) -- `Copy your github fork locally - `_ -- Create a branch to identify the issue you would like to work - on (eg 2287-dry-test-suite) -- Using your favorite editor, make your changes, - `committing as you go `_ -- Include tests if your patch is supposed to solve a bug, and explain - clearly under which circumstances the bug happens. Make sure the test fails - without your patch. -- Try to follow `PEP8 `_, but you - may ignore the line-length-limit if following it would make the code uglier. -- `Run tests. - `_ - When tests pass push changes to github and `create pull request - `_ +First time setup +---------------- + +- Download and install the `latest version of git`_. +- Configure git with your `username`_ and `email`_. +- Make sure you have a `GitHub account`_. +- Fork Flask to your GitHub account by clicking the `Fork`_ button. +- `Clone`_ your GitHub fork locally. +- Add the main repository as a remote to update later. + ``git remote add pallets https://github.com/pallets/flask`` + +.. _GitHub account: https://github.com/join +.. _latest version of git: https://git-scm.com/downloads +.. _username: https://help.github.com/articles/setting-your-username-in-git/ +.. _email: https://help.github.com/articles/setting-your-email-in-git/ +.. _Fork: https://github.com/pallets/flask/pull/2305#fork-destination-box +.. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork + +Start coding +------------ + +- Create a branch to identify the issue you would like to work on (e.g. + ``2287-dry-test-suite``) +- Using your favorite editor, make your changes, `committing as you go`_. +- Try to follow `PEP8`_, but you may ignore the line length limit if following + it would make the code uglier. +- Include tests that cover any code changes you make. Make sure the test fails + without your patch. `Run the tests. `_. +- Push your commits to GitHub and `create a pull request`_. - Celebrate 🎉 +.. _committing as you go: http://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes +.. _PEP8: https://pep8.org/ +.. _create a pull request: https://help.github.com/articles/creating-a-pull-request/ + +.. _contributing-testsuite: Running the testsuite --------------------- From 954d9ca0b8519ee651daba64ca9292aaa74fff0d Mon Sep 17 00:00:00 2001 From: Levi Roth Date: Tue, 23 May 2017 14:30:39 -0400 Subject: [PATCH 419/440] Added documentation for PowerShell environment variables --- docs/quickstart.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index aea776f3..d56fa8e2 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -50,7 +50,14 @@ to tell your terminal the application to work with by exporting the $ flask run * Running on http://127.0.0.1:5000/ -If you are on Windows you need to use ``set`` instead of ``export``. +If you are on Windows, the environment variable syntax depends on command line +interpreter. On Command Prompt:: + + C:\path\to\app>set FLASK_APP=hello.py + +And on PowerShell:: + + PS C:\path\to\app> $env:FLASK_APP = "hello.py" Alternatively you can use :command:`python -m flask`:: From 8858135043a42df9f0728eb548d3272b476100b6 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 23 May 2017 11:50:14 -0700 Subject: [PATCH 420/440] Update testing.rst --- docs/testing.rst | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 67b8aaea..c00d06e0 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -47,17 +47,23 @@ the application for testing and initializes a new database.:: import os import tempfile + import pytest + from flaskr import flaskr + @pytest.fixture def client(request): db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() flaskr.app.config['TESTING'] = True client = flaskr.app.test_client() + with flaskr.app.app_context(): flaskr.init_db() + yield client + os.close(db_fd) os.unlink(flaskr.app.config['DATABASE']) @@ -77,8 +83,8 @@ low-level file handle and a random file name, the latter we use as database name. We just have to keep the `db_fd` around so that we can use the :func:`os.close` function to close the file. -To delete the database after the test, we close the file and remove it -from the filesystem. +To delete the database after the test, the fixture closes the file and removes +it from the filesystem. If we now run the test suite, we should see the following output:: @@ -107,6 +113,7 @@ test function to :file:`test_flaskr.py`, like this:: def test_empty_db(client): """Start with a blank database.""" + rv = client.get('/') assert b'No entries here so far' in rv.data @@ -148,6 +155,7 @@ Add the following two functions to your :file:`test_flaskr.py` file:: password=password ), follow_redirects=True) + def logout(client): return client.get('/logout', follow_redirects=True) @@ -155,17 +163,18 @@ Now we can easily test that logging in and out works and that it fails with invalid credentials. Add this new test function:: def test_login_logout(client): - """Make sure login and logout works""" - rv = login(client, flaskr.app.config['USERNAME'], - flaskr.app.config['PASSWORD']) + """Make sure login and logout works.""" + + rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD']) assert b'You were logged in' in rv.data + rv = logout(client) assert b'You were logged out' in rv.data - rv = login(client, flaskr.app.config['USERNAME'] + 'x', - flaskr.app.config['PASSWORD']) + + rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD']) assert b'Invalid username' in rv.data - rv = login(client, flaskr.app.config['USERNAME'], - flaskr.app.config['PASSWORD'] + 'x') + + rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x') assert b'Invalid password' in rv.data Test Adding Messages @@ -175,9 +184,9 @@ We should also test that adding messages works. Add a new test function like this:: def test_messages(client): - """Test that messages work""" - login(client, flaskr.app.config['USERNAME'], - flaskr.app.config['PASSWORD']) + """Test that messages work.""" + + login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD']) rv = client.post('/add', data=dict( title='', text='HTML allowed here' @@ -207,11 +216,9 @@ For more complex tests with headers and status codes, check out the `MiniTwit Example`_ from the sources which contains a larger test suite. - .. _MiniTwit Example: https://github.com/pallets/flask/tree/master/examples/minitwit/ - Other Testing Tricks -------------------- From 75f537fb87ea3d60465a82c5c221104bcceef1f2 Mon Sep 17 00:00:00 2001 From: kaveh Date: Tue, 23 May 2017 11:51:13 -0700 Subject: [PATCH 421/440] Adds provide_automatic_options to Class-based Views --- flask/views.py | 4 ++++ tests/test_views.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/flask/views.py b/flask/views.py index 848ccb0b..b3027970 100644 --- a/flask/views.py +++ b/flask/views.py @@ -51,6 +51,9 @@ class View(object): #: A list of methods this view can handle. methods = None + #: Setting this disables or force-enables the automatic options handling. + provide_automatic_options = None + #: The canonical way to decorate class-based views is to decorate the #: return value of as_view(). However since this moves parts of the #: logic from the class declaration to the place where it's hooked @@ -99,6 +102,7 @@ class View(object): view.__doc__ = cls.__doc__ view.__module__ = cls.__module__ view.methods = cls.methods + view.provide_automatic_options = cls.provide_automatic_options return view diff --git a/tests/test_views.py b/tests/test_views.py index 65981dbd..896880c0 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -109,6 +109,43 @@ def test_view_decorators(): assert rv.headers['X-Parachute'] == 'awesome' assert rv.data == b'Awesome' +def test_view_provide_automatic_options_attr(): + app = flask.Flask(__name__) + + class Index1(flask.views.View): + provide_automatic_options = False + def dispatch_request(self): + return 'Hello World!' + + app.add_url_rule('/', view_func=Index1.as_view('index')) + c = app.test_client() + rv = c.open('/', method='OPTIONS') + assert rv.status_code == 405 + + app = flask.Flask(__name__) + + class Index2(flask.views.View): + methods = ['OPTIONS'] + provide_automatic_options = True + def dispatch_request(self): + return 'Hello World!' + + app.add_url_rule('/', view_func=Index2.as_view('index')) + c = app.test_client() + rv = c.open('/', method='OPTIONS') + assert sorted(rv.allow) == ['OPTIONS'] + + app = flask.Flask(__name__) + + class Index3(flask.views.View): + def dispatch_request(self): + return 'Hello World!' + + app.add_url_rule('/', view_func=Index3.as_view('index')) + c = app.test_client() + rv = c.open('/', method='OPTIONS') + assert 'OPTIONS' in rv.allow + def test_implicit_head(): app = flask.Flask(__name__) From fe27d04cc196d65af17ac6cf67574fa61d5b4828 Mon Sep 17 00:00:00 2001 From: bovarysme Date: Tue, 23 May 2017 22:22:16 +0200 Subject: [PATCH 422/440] Fix a small oversight in the testing docs --- docs/testing.rst | 2 +- examples/flaskr/tests/test_flaskr.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index c00d06e0..fbd3fad5 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -54,7 +54,7 @@ the application for testing and initializes a new database.:: @pytest.fixture - def client(request): + def client(): db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() flaskr.app.config['TESTING'] = True client = flaskr.app.test_client() diff --git a/examples/flaskr/tests/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py index df32cd4b..493067ee 100644 --- a/examples/flaskr/tests/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -16,7 +16,7 @@ from flaskr import flaskr @pytest.fixture -def client(request): +def client(): db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() flaskr.app.config['TESTING'] = True client = flaskr.app.test_client() From ae41df9a77ceab4469029b043e38333b8654e1da Mon Sep 17 00:00:00 2001 From: Hendrik Makait Date: Tue, 23 May 2017 13:46:45 -0700 Subject: [PATCH 423/440] Check if app factory takes script_info argument and call it with(out) script_info as an argument depending on that --- flask/_compat.py | 2 ++ flask/cli.py | 30 +++++++++++++++++------ tests/test_cli.py | 62 +++++++++++++++++++++++++++++++---------------- 3 files changed, 66 insertions(+), 28 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index 071628fc..173b3689 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -25,6 +25,7 @@ if not PY2: itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items()) + from inspect import getfullargspec as getargspec from io import StringIO def reraise(tp, value, tb=None): @@ -43,6 +44,7 @@ else: itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() + from inspect import getargspec from cStringIO import StringIO exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') diff --git a/flask/cli.py b/flask/cli.py index 6aa66f4f..ea294ae6 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -22,13 +22,14 @@ from . import __version__ from ._compat import iteritems, reraise from .globals import current_app from .helpers import get_debug_flag +from ._compat import getargspec class NoAppException(click.UsageError): """Raised if an application cannot be found or loaded.""" -def find_best_app(module): +def find_best_app(script_info, module): """Given a module instance this tries to find the best possible application in the module or raises an exception. """ @@ -60,8 +61,8 @@ def find_best_app(module): if callable(app_factory): try: - app = app_factory() - + app = check_factory_for_script_info_and_call(app_factory, + script_info) if isinstance(app, Flask): return app except TypeError: @@ -79,6 +80,21 @@ def find_best_app(module): ) +def check_factory_for_script_info_and_call(func, script_info): + """Given a function this checks if the function has an argument named + script_info or just a single argument and calls the function with + script_info if so. Otherwise calls the function without any arguments and + returns the result.""" + arguments = getargspec(func).args + if 'script_info' in arguments: + result = func(script_info=script_info) + elif len(arguments) == 1: + result = func(script_info) + else: + result = func() + return result + + def prepare_exec_for_file(filename): """Given a filename this will try to calculate the python path, add it to the search path and return the actual module name that is expected. @@ -108,7 +124,7 @@ def prepare_exec_for_file(filename): return '.'.join(module[::-1]) -def locate_app(app_id): +def locate_app(script_info, app_id): """Attempts to locate the application.""" __traceback_hide__ = True if ':' in app_id: @@ -134,7 +150,7 @@ def locate_app(app_id): mod = sys.modules[module] if app_obj is None: - app = find_best_app(mod) + app = find_best_app(script_info, mod) else: app = getattr(mod, app_obj, None) if app is None: @@ -259,7 +275,7 @@ class ScriptInfo(object): if self._loaded_app is not None: return self._loaded_app if self.create_app is not None: - rv = self.create_app(self) + rv = check_factory_for_script_info_and_call(self.create_app, self) else: if not self.app_import_path: raise NoAppException( @@ -267,7 +283,7 @@ class ScriptInfo(object): 'the FLASK_APP environment variable.\n\nFor more ' 'information see ' 'http://flask.pocoo.org/docs/latest/quickstart/') - rv = locate_app(self.app_import_path) + rv = locate_app(self, self.app_import_path) debug = get_debug_flag() if debug is not None: rv.debug = debug diff --git a/tests/test_cli.py b/tests/test_cli.py index bbb6fe58..1c843d61 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -39,60 +39,75 @@ def test_cli_name(test_apps): def test_find_best_app(test_apps): """Test if `find_best_app` behaves as expected with different combinations of input.""" + script_info = ScriptInfo() class Module: app = Flask('appname') - assert find_best_app(Module) == Module.app + assert find_best_app(script_info, Module) == Module.app class Module: application = Flask('appname') - assert find_best_app(Module) == Module.application + assert find_best_app(script_info, Module) == Module.application class Module: myapp = Flask('appname') - assert find_best_app(Module) == Module.myapp + assert find_best_app(script_info, Module) == Module.myapp class Module: @staticmethod def create_app(): return Flask('appname') - assert isinstance(find_best_app(Module), Flask) - assert find_best_app(Module).name == 'appname' + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def create_app(foo): + return Flask('appname') + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' + + class Module: + @staticmethod + def create_app(foo=None, script_info=None): + return Flask('appname') + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' class Module: @staticmethod def make_app(): return Flask('appname') - assert isinstance(find_best_app(Module), Flask) - assert find_best_app(Module).name == 'appname' + assert isinstance(find_best_app(script_info, Module), Flask) + assert find_best_app(script_info, Module).name == 'appname' class Module: myapp = Flask('appname1') @staticmethod def create_app(): return Flask('appname2') - assert find_best_app(Module) == Module.myapp + assert find_best_app(script_info, Module) == Module.myapp class Module: myapp = Flask('appname1') @staticmethod - def create_app(foo): + def create_app(): return Flask('appname2') - assert find_best_app(Module) == Module.myapp + assert find_best_app(script_info, Module) == Module.myapp class Module: pass - pytest.raises(NoAppException, find_best_app, Module) + pytest.raises(NoAppException, find_best_app, script_info, Module) class Module: myapp1 = Flask('appname1') myapp2 = Flask('appname2') - pytest.raises(NoAppException, find_best_app, Module) + pytest.raises(NoAppException, find_best_app, script_info, Module) class Module: @staticmethod - def create_app(foo): + def create_app(foo, bar): return Flask('appname2') - pytest.raises(NoAppException, find_best_app, Module) + pytest.raises(NoAppException, find_best_app, script_info, Module) def test_prepare_exec_for_file(test_apps): @@ -117,13 +132,18 @@ def test_prepare_exec_for_file(test_apps): def test_locate_app(test_apps): """Test of locate_app.""" - assert locate_app("cliapp.app").name == "testapp" - assert locate_app("cliapp.app:testapp").name == "testapp" - assert locate_app("cliapp.multiapp:app1").name == "app1" - pytest.raises(NoAppException, locate_app, "notanpp.py") - pytest.raises(NoAppException, locate_app, "cliapp/app") - pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") - pytest.raises(NoAppException, locate_app, "cliapp.importerrorapp") + script_info = ScriptInfo() + assert locate_app(script_info, "cliapp.app").name == "testapp" + assert locate_app(script_info, "cliapp.app:testapp").name == "testapp" + assert locate_app(script_info, "cliapp.multiapp:app1").name == "app1" + pytest.raises(NoAppException, locate_app, + script_info, "notanpp.py") + pytest.raises(NoAppException, locate_app, + script_info, "cliapp/app") + pytest.raises(RuntimeError, locate_app, + script_info, "cliapp.app:notanapp") + pytest.raises(NoAppException, locate_app, + script_info, "cliapp.importerrorapp") def test_find_default_import_path(test_apps, monkeypatch, tmpdir): From 5b0b9717da958fd6325c675d92be4f6667796112 Mon Sep 17 00:00:00 2001 From: Christian Stade-Schuldt Date: Tue, 23 May 2017 15:18:39 -0700 Subject: [PATCH 424/440] DRYing up the test suite using pytest fixtures (#2306) * add fixtures to conftest.py * use fixtures in test_appctx.py * use fixtures in test_blueprints.py * use fixtures in test_depreciations.py * use fixtures in test_regressions.py * use fixtures in test_reqctx.py * use fixtures in test_templating.py * use fixtures in test_user_error_handler.py * use fixtures in test_views.py * use fixtures in test_basics.py * use fixtures in test_helpers.py * use fixtures in test_testing.py * update conftest.py * make docstrings PEP-257 compliant * cleanup * switch dictonary format * use pytest parameterization for test_json_as_unicode --- tests/conftest.py | 52 ++- tests/test_appctx.py | 96 ++--- tests/test_basic.py | 487 +++++++++++------------- tests/test_blueprints.py | 280 ++++++++------ tests/test_deprecations.py | 13 +- tests/test_helpers.py | 615 +++++++++++++++---------------- tests/test_regression.py | 7 +- tests/test_reqctx.py | 51 +-- tests/test_templating.py | 210 ++++++----- tests/test_testing.py | 96 ++--- tests/test_user_error_handler.py | 22 +- tests/test_views.py | 44 ++- 12 files changed, 999 insertions(+), 974 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index eb130db6..40b1e88f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,40 @@ import sys import pkgutil import pytest import textwrap +from flask import Flask as _Flask + + +class Flask(_Flask): + testing = True + secret_key = __name__ + + def make_response(self, rv): + if rv is None: + rv = '' + return super(Flask, self).make_response(rv) + + +@pytest.fixture +def app(): + app = Flask(__name__) + return app + + +@pytest.fixture +def app_ctx(app): + with app.app_context() as ctx: + yield ctx + + +@pytest.fixture +def req_ctx(app): + with app.test_request_context() as ctx: + yield ctx + + +@pytest.fixture +def client(app): + return app.test_client() @pytest.fixture @@ -63,12 +97,13 @@ def limit_loader(request, monkeypatch): def get_loader(*args, **kwargs): return LimitedLoader(old_get_loader(*args, **kwargs)) + monkeypatch.setattr(pkgutil, 'get_loader', get_loader) @pytest.fixture def modules_tmpdir(tmpdir, monkeypatch): - '''A tmpdir added to sys.path''' + """A tmpdir added to sys.path.""" rv = tmpdir.mkdir('modules_tmpdir') monkeypatch.syspath_prepend(str(rv)) return rv @@ -82,10 +117,10 @@ def modules_tmpdir_prefix(modules_tmpdir, monkeypatch): @pytest.fixture def site_packages(modules_tmpdir, monkeypatch): - '''Create a fake site-packages''' + """Create a fake site-packages.""" rv = modules_tmpdir \ - .mkdir('lib')\ - .mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info))\ + .mkdir('lib') \ + .mkdir('python{x[0]}.{x[1]}'.format(x=sys.version_info)) \ .mkdir('site-packages') monkeypatch.syspath_prepend(str(rv)) return rv @@ -93,8 +128,9 @@ def site_packages(modules_tmpdir, monkeypatch): @pytest.fixture def install_egg(modules_tmpdir, monkeypatch): - '''Generate egg from package name inside base and put the egg into - sys.path''' + """Generate egg from package name inside base and put the egg into + sys.path.""" + def inner(name, base=modules_tmpdir): if not isinstance(name, str): raise ValueError(name) @@ -118,6 +154,7 @@ def install_egg(modules_tmpdir, monkeypatch): egg_path, = modules_tmpdir.join('dist/').listdir() monkeypatch.syspath_prepend(str(egg_path)) return egg_path + return inner @@ -125,6 +162,7 @@ def install_egg(modules_tmpdir, monkeypatch): def purge_module(request): def inner(name): request.addfinalizer(lambda: sys.modules.pop(name, None)) + return inner @@ -132,4 +170,4 @@ def purge_module(request): def catch_deprecation_warnings(recwarn): yield gc.collect() - assert not recwarn.list + assert not recwarn.list, '\n'.join(str(w.message) for w in recwarn.list) diff --git a/tests/test_appctx.py b/tests/test_appctx.py index 13b61eee..7ef7b479 100644 --- a/tests/test_appctx.py +++ b/tests/test_appctx.py @@ -14,8 +14,7 @@ import pytest import flask -def test_basic_url_generation(): - app = flask.Flask(__name__) +def test_basic_url_generation(app): app.config['SERVER_NAME'] = 'localhost' app.config['PREFERRED_URL_SCHEME'] = 'https' @@ -27,31 +26,33 @@ def test_basic_url_generation(): rv = flask.url_for('index') assert rv == 'https://localhost/' -def test_url_generation_requires_server_name(): - app = flask.Flask(__name__) + +def test_url_generation_requires_server_name(app): with app.app_context(): with pytest.raises(RuntimeError): flask.url_for('index') + def test_url_generation_without_context_fails(): with pytest.raises(RuntimeError): flask.url_for('index') -def test_request_context_means_app_context(): - app = flask.Flask(__name__) + +def test_request_context_means_app_context(app): with app.test_request_context(): assert flask.current_app._get_current_object() == app assert flask._app_ctx_stack.top is None -def test_app_context_provides_current_app(): - app = flask.Flask(__name__) + +def test_app_context_provides_current_app(app): with app.app_context(): assert flask.current_app._get_current_object() == app assert flask._app_ctx_stack.top is None -def test_app_tearing_down(): + +def test_app_tearing_down(app): cleanup_stuff = [] - app = flask.Flask(__name__) + @app.teardown_appcontext def cleanup(exception): cleanup_stuff.append(exception) @@ -61,9 +62,10 @@ def test_app_tearing_down(): assert cleanup_stuff == [None] -def test_app_tearing_down_with_previous_exception(): + +def test_app_tearing_down_with_previous_exception(app): cleanup_stuff = [] - app = flask.Flask(__name__) + @app.teardown_appcontext def cleanup(exception): cleanup_stuff.append(exception) @@ -78,9 +80,10 @@ def test_app_tearing_down_with_previous_exception(): assert cleanup_stuff == [None] -def test_app_tearing_down_with_handled_exception(): + +def test_app_tearing_down_with_handled_exception(app): cleanup_stuff = [] - app = flask.Flask(__name__) + @app.teardown_appcontext def cleanup(exception): cleanup_stuff.append(exception) @@ -93,46 +96,49 @@ def test_app_tearing_down_with_handled_exception(): assert cleanup_stuff == [None] -def test_app_ctx_globals_methods(): - app = flask.Flask(__name__) - with app.app_context(): - # get - assert flask.g.get('foo') is None - assert flask.g.get('foo', 'bar') == 'bar' - # __contains__ - assert 'foo' not in flask.g - flask.g.foo = 'bar' - assert 'foo' in flask.g - # setdefault - flask.g.setdefault('bar', 'the cake is a lie') - flask.g.setdefault('bar', 'hello world') - assert flask.g.bar == 'the cake is a lie' - # pop - assert flask.g.pop('bar') == 'the cake is a lie' - with pytest.raises(KeyError): - flask.g.pop('bar') - assert flask.g.pop('bar', 'more cake') == 'more cake' - # __iter__ - assert list(flask.g) == ['foo'] - -def test_custom_app_ctx_globals_class(): + +def test_app_ctx_globals_methods(app, app_ctx): + # get + assert flask.g.get('foo') is None + assert flask.g.get('foo', 'bar') == 'bar' + # __contains__ + assert 'foo' not in flask.g + flask.g.foo = 'bar' + assert 'foo' in flask.g + # setdefault + flask.g.setdefault('bar', 'the cake is a lie') + flask.g.setdefault('bar', 'hello world') + assert flask.g.bar == 'the cake is a lie' + # pop + assert flask.g.pop('bar') == 'the cake is a lie' + with pytest.raises(KeyError): + flask.g.pop('bar') + assert flask.g.pop('bar', 'more cake') == 'more cake' + # __iter__ + assert list(flask.g) == ['foo'] + + +def test_custom_app_ctx_globals_class(app): class CustomRequestGlobals(object): def __init__(self): self.spam = 'eggs' - app = flask.Flask(__name__) + app.app_ctx_globals_class = CustomRequestGlobals with app.app_context(): assert flask.render_template_string('{{ g.spam }}') == 'eggs' -def test_context_refcounts(): + +def test_context_refcounts(app, client): called = [] - app = flask.Flask(__name__) + @app.teardown_request def teardown_req(error=None): called.append('request') + @app.teardown_appcontext def teardown_app(error=None): called.append('app') + @app.route('/') def index(): with flask._app_ctx_stack.top: @@ -141,16 +147,16 @@ def test_context_refcounts(): env = flask._request_ctx_stack.top.request.environ assert env['werkzeug.request'] is not None return u'' - c = app.test_client() - res = c.get('/') + + res = client.get('/') assert res.status_code == 200 assert res.data == b'' assert called == ['request', 'app'] -def test_clean_pop(): +def test_clean_pop(app): + app.testing = False called = [] - app = flask.Flask(__name__) @app.teardown_request def teardown_req(error=None): @@ -166,5 +172,5 @@ def test_clean_pop(): except ZeroDivisionError: pass - assert called == ['test_appctx', 'TEARDOWN'] + assert called == ['conftest', 'TEARDOWN'] assert not flask.current_app diff --git a/tests/test_basic.py b/tests/test_basic.py index 54f4c8e6..9d72f6d1 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -25,20 +25,17 @@ from werkzeug.routing import BuildError import werkzeug.serving -def test_options_work(): - app = flask.Flask(__name__) - +def test_options_work(app, client): @app.route('/', methods=['GET', 'POST']) def index(): return 'Hello World' - rv = app.test_client().open('/', method='OPTIONS') + + rv = client.open('/', method='OPTIONS') assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] assert rv.data == b'' -def test_options_on_multiple_rules(): - app = flask.Flask(__name__) - +def test_options_on_multiple_rules(app, client): @app.route('/', methods=['GET', 'POST']) def index(): return 'Hello World' @@ -46,7 +43,8 @@ def test_options_on_multiple_rules(): @app.route('/', methods=['PUT']) def index_put(): return 'Aha!' - rv = app.test_client().open('/', method='OPTIONS') + + rv = client.open('/', method='OPTIONS') assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] @@ -55,6 +53,7 @@ def test_provide_automatic_options_attr(): def index(): return 'Hello World!' + index.provide_automatic_options = False app.route('/')(index) rv = app.test_client().open('/', method='OPTIONS') @@ -64,15 +63,14 @@ def test_provide_automatic_options_attr(): def index2(): return 'Hello World!' + index2.provide_automatic_options = True app.route('/', methods=['OPTIONS'])(index2) rv = app.test_client().open('/', method='OPTIONS') assert sorted(rv.allow) == ['OPTIONS'] -def test_provide_automatic_options_kwarg(): - app = flask.Flask(__name__) - +def test_provide_automatic_options_kwarg(app, client): def index(): return flask.request.method @@ -84,43 +82,39 @@ def test_provide_automatic_options_kwarg(): '/more', view_func=more, methods=['GET', 'POST'], provide_automatic_options=False ) + assert client.get('/').data == b'GET' - c = app.test_client() - assert c.get('/').data == b'GET' - - rv = c.post('/') + rv = client.post('/') assert rv.status_code == 405 assert sorted(rv.allow) == ['GET', 'HEAD'] # Older versions of Werkzeug.test.Client don't have an options method - if hasattr(c, 'options'): - rv = c.options('/') + if hasattr(client, 'options'): + rv = client.options('/') else: - rv = c.open('/', method='OPTIONS') + rv = client.open('/', method='OPTIONS') assert rv.status_code == 405 - rv = c.head('/') + rv = client.head('/') assert rv.status_code == 200 assert not rv.data # head truncates - assert c.post('/more').data == b'POST' - assert c.get('/more').data == b'GET' + assert client.post('/more').data == b'POST' + assert client.get('/more').data == b'GET' - rv = c.delete('/more') + rv = client.delete('/more') assert rv.status_code == 405 assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] - if hasattr(c, 'options'): - rv = c.options('/more') + if hasattr(client, 'options'): + rv = client.options('/more') else: - rv = c.open('/more', method='OPTIONS') + rv = client.open('/more', method='OPTIONS') assert rv.status_code == 405 -def test_request_dispatching(): - app = flask.Flask(__name__) - +def test_request_dispatching(app, client): @app.route('/') def index(): return flask.request.method @@ -129,32 +123,28 @@ def test_request_dispatching(): def more(): return flask.request.method - c = app.test_client() - assert c.get('/').data == b'GET' - rv = c.post('/') + assert client.get('/').data == b'GET' + rv = client.post('/') assert rv.status_code == 405 assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] - rv = c.head('/') + rv = client.head('/') assert rv.status_code == 200 assert not rv.data # head truncates - assert c.post('/more').data == b'POST' - assert c.get('/more').data == b'GET' - rv = c.delete('/more') + assert client.post('/more').data == b'POST' + assert client.get('/more').data == b'GET' + rv = client.delete('/more') assert rv.status_code == 405 assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] -def test_disallow_string_for_allowed_methods(): - app = flask.Flask(__name__) +def test_disallow_string_for_allowed_methods(app): with pytest.raises(TypeError): @app.route('/', methods='GET POST') def index(): return "Hey" -def test_url_mapping(): - app = flask.Flask(__name__) - +def test_url_mapping(app, client): random_uuid4 = "7eb41166-9ebf-4d26-b771-ea3f54f8b383" def index(): @@ -166,34 +156,31 @@ def test_url_mapping(): def options(): return random_uuid4 - app.add_url_rule('/', 'index', index) app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) # Issue 1288: Test that automatic options are not added when non-uppercase 'options' in methods app.add_url_rule('/options', 'options', options, methods=['options']) - c = app.test_client() - assert c.get('/').data == b'GET' - rv = c.post('/') + assert client.get('/').data == b'GET' + rv = client.post('/') assert rv.status_code == 405 assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] - rv = c.head('/') + rv = client.head('/') assert rv.status_code == 200 assert not rv.data # head truncates - assert c.post('/more').data == b'POST' - assert c.get('/more').data == b'GET' - rv = c.delete('/more') + assert client.post('/more').data == b'POST' + assert client.get('/more').data == b'GET' + rv = client.delete('/more') assert rv.status_code == 405 assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - rv = c.open('/options', method='OPTIONS') + rv = client.open('/options', method='OPTIONS') assert rv.status_code == 200 assert random_uuid4 in rv.data.decode("utf-8") -def test_werkzeug_routing(): +def test_werkzeug_routing(app, client): from werkzeug.routing import Submount, Rule - app = flask.Flask(__name__) app.url_map.add(Submount('/foo', [ Rule('/bar', endpoint='bar'), Rule('/', endpoint='index') @@ -204,17 +191,16 @@ def test_werkzeug_routing(): def index(): return 'index' + app.view_functions['bar'] = bar app.view_functions['index'] = index - c = app.test_client() - assert c.get('/foo/').data == b'index' - assert c.get('/foo/bar').data == b'bar' + assert client.get('/foo/').data == b'index' + assert client.get('/foo/bar').data == b'bar' -def test_endpoint_decorator(): +def test_endpoint_decorator(app, client): from werkzeug.routing import Submount, Rule - app = flask.Flask(__name__) app.url_map.add(Submount('/foo', [ Rule('/bar', endpoint='bar'), Rule('/', endpoint='index') @@ -228,13 +214,11 @@ def test_endpoint_decorator(): def index(): return 'index' - c = app.test_client() - assert c.get('/foo/').data == b'index' - assert c.get('/foo/bar').data == b'bar' + assert client.get('/foo/').data == b'index' + assert client.get('/foo/bar').data == b'bar' -def test_session(): - app = flask.Flask(__name__) +def test_session(app, client): app.secret_key = 'testkey' @app.route('/set', methods=['POST']) @@ -246,13 +230,11 @@ def test_session(): def get(): return flask.session['value'] - c = app.test_client() - assert c.post('/set', data={'value': '42'}).data == b'value set' - assert c.get('/get').data == b'42' + assert client.post('/set', data={'value': '42'}).data == b'value set' + assert client.get('/get').data == b'42' -def test_session_using_server_name(): - app = flask.Flask(__name__) +def test_session_using_server_name(app, client): app.config.update( SECRET_KEY='foo', SERVER_NAME='example.com' @@ -262,13 +244,13 @@ def test_session_using_server_name(): def index(): flask.session['testing'] = 42 return 'Hello World' - rv = app.test_client().get('/', 'http://example.com/') + + rv = client.get('/', 'http://example.com/') assert 'domain=.example.com' in rv.headers['set-cookie'].lower() assert 'httponly' in rv.headers['set-cookie'].lower() -def test_session_using_server_name_and_port(): - app = flask.Flask(__name__) +def test_session_using_server_name_and_port(app, client): app.config.update( SECRET_KEY='foo', SERVER_NAME='example.com:8080' @@ -278,13 +260,13 @@ def test_session_using_server_name_and_port(): def index(): flask.session['testing'] = 42 return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/') + + rv = client.get('/', 'http://example.com:8080/') assert 'domain=.example.com' in rv.headers['set-cookie'].lower() assert 'httponly' in rv.headers['set-cookie'].lower() -def test_session_using_server_name_port_and_path(): - app = flask.Flask(__name__) +def test_session_using_server_name_port_and_path(app, client): app.config.update( SECRET_KEY='foo', SERVER_NAME='example.com:8080', @@ -295,15 +277,15 @@ def test_session_using_server_name_port_and_path(): def index(): flask.session['testing'] = 42 return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/foo') + + rv = client.get('/', 'http://example.com:8080/foo') assert 'domain=example.com' in rv.headers['set-cookie'].lower() assert 'path=/foo' in rv.headers['set-cookie'].lower() assert 'httponly' in rv.headers['set-cookie'].lower() -def test_session_using_application_root(): +def test_session_using_application_root(app, client): class PrefixPathMiddleware(object): - def __init__(self, app, prefix): self.app = app self.prefix = prefix @@ -312,7 +294,6 @@ def test_session_using_application_root(): environ['SCRIPT_NAME'] = self.prefix return self.app(environ, start_response) - app = flask.Flask(__name__) app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') app.config.update( SECRET_KEY='foo', @@ -323,12 +304,12 @@ def test_session_using_application_root(): def index(): flask.session['testing'] = 42 return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/') + + rv = client.get('/', 'http://example.com:8080/') assert 'path=/bar' in rv.headers['set-cookie'].lower() -def test_session_using_session_settings(): - app = flask.Flask(__name__) +def test_session_using_session_settings(app, client): app.config.update( SECRET_KEY='foo', SERVER_NAME='www.example.com:8080', @@ -343,7 +324,8 @@ def test_session_using_session_settings(): def index(): flask.session['testing'] = 42 return 'Hello World' - rv = app.test_client().get('/', 'http://www.example.com:8080/test/') + + rv = client.get('/', 'http://www.example.com:8080/test/') cookie = rv.headers['set-cookie'].lower() assert 'domain=.example.com' in cookie assert 'path=/' in cookie @@ -351,8 +333,7 @@ def test_session_using_session_settings(): assert 'httponly' not in cookie -def test_session_localhost_warning(recwarn): - app = flask.Flask(__name__) +def test_session_localhost_warning(recwarn, app, client): app.config.update( SECRET_KEY='testing', SERVER_NAME='localhost:5000', @@ -363,14 +344,13 @@ def test_session_localhost_warning(recwarn): flask.session['testing'] = 42 return 'testing' - rv = app.test_client().get('/', 'http://localhost:5000/') + rv = client.get('/', 'http://localhost:5000/') assert 'domain' not in rv.headers['set-cookie'].lower() w = recwarn.pop(UserWarning) assert '"localhost" is not a valid cookie domain' in str(w.message) -def test_session_ip_warning(recwarn): - app = flask.Flask(__name__) +def test_session_ip_warning(recwarn, app, client): app.config.update( SECRET_KEY='testing', SERVER_NAME='127.0.0.1:5000', @@ -381,7 +361,7 @@ def test_session_ip_warning(recwarn): flask.session['testing'] = 42 return 'testing' - rv = app.test_client().get('/', 'http://127.0.0.1:5000/') + rv = client.get('/', 'http://127.0.0.1:5000/') assert 'domain=127.0.0.1' in rv.headers['set-cookie'].lower() w = recwarn.pop(UserWarning) assert 'cookie domain is an IP' in str(w.message) @@ -393,15 +373,15 @@ def test_missing_session(): def expect_exception(f, *args, **kwargs): e = pytest.raises(RuntimeError, f, *args, **kwargs) assert e.value.args and 'session is unavailable' in e.value.args[0] + with app.test_request_context(): assert flask.session.get('missing_key') is None expect_exception(flask.session.__setitem__, 'foo', 42) expect_exception(flask.session.pop, 'foo') -def test_session_expiration(): +def test_session_expiration(app, client): permanent = True - app = flask.Flask(__name__) app.secret_key = 'testkey' @app.route('/') @@ -414,7 +394,6 @@ def test_session_expiration(): def test(): return text_type(flask.session.permanent) - client = app.test_client() rv = client.get('/') assert 'set-cookie' in rv.headers match = re.search(r'(?i)\bexpires=([^;]+)', rv.headers['set-cookie']) @@ -428,16 +407,14 @@ def test_session_expiration(): assert rv.data == b'True' permanent = False - rv = app.test_client().get('/') + rv = client.get('/') assert 'set-cookie' in rv.headers match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) assert match is None -def test_session_stored_last(): - app = flask.Flask(__name__) +def test_session_stored_last(app, client): app.secret_key = 'development-key' - app.testing = True @app.after_request def modify_session(response): @@ -448,15 +425,12 @@ def test_session_stored_last(): def dump_session_contents(): return repr(flask.session.get('foo')) - c = app.test_client() - assert c.get('/').data == b'None' - assert c.get('/').data == b'42' + assert client.get('/').data == b'None' + assert client.get('/').data == b'42' -def test_session_special_types(): - app = flask.Flask(__name__) +def test_session_special_types(app, client): app.secret_key = 'development-key' - app.testing = True now = datetime.utcnow().replace(microsecond=0) the_uuid = uuid.uuid4() @@ -473,9 +447,8 @@ def test_session_special_types(): def dump_session_contents(): return pickle.dumps(dict(flask.session)) - c = app.test_client() - c.get('/') - rv = pickle.loads(c.get('/').data) + client.get('/') + rv = pickle.loads(client.get('/').data) assert rv['m'] == flask.Markup('Hello!') assert type(rv['m']) == flask.Markup assert rv['dt'] == now @@ -485,9 +458,7 @@ def test_session_special_types(): assert rv['t'] == (1, 2, 3) -def test_session_cookie_setting(): - app = flask.Flask(__name__) - app.testing = True +def test_session_cookie_setting(app): app.secret_key = 'dev key' is_permanent = True @@ -529,8 +500,7 @@ def test_session_cookie_setting(): run_test(expect_header=False) -def test_session_vary_cookie(): - app = flask.Flask(__name__) +def test_session_vary_cookie(app, client): app.secret_key = 'testkey' @app.route('/set') @@ -554,10 +524,8 @@ def test_session_vary_cookie(): def no_vary_header(): return '' - c = app.test_client() - def expect(path, header=True): - rv = c.get(path) + rv = client.get(path) if header: assert rv.headers['Vary'] == 'Cookie' @@ -571,29 +539,25 @@ def test_session_vary_cookie(): expect('/no-vary-header', False) -def test_flashes(): - app = flask.Flask(__name__) +def test_flashes(app, req_ctx): app.secret_key = 'testkey' - with app.test_request_context(): - assert not flask.session.modified - flask.flash('Zap') - flask.session.modified = False - flask.flash('Zip') - assert flask.session.modified - assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + assert not flask.session.modified + flask.flash('Zap') + flask.session.modified = False + flask.flash('Zip') + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] -def test_extended_flashing(): +def test_extended_flashing(app): # Be sure app.testing=True below, else tests can fail silently. # # Specifically, if app.testing is not set to True, the AssertionErrors # in the view functions will cause a 500 response to the test client # instead of propagating exceptions. - app = flask.Flask(__name__) app.secret_key = 'testkey' - app.testing = True @app.route('/') def index(): @@ -651,29 +615,24 @@ def test_extended_flashing(): # Create new test client on each test to clean flashed messages. - c = app.test_client() - c.get('/') - c.get('/test/') - - c = app.test_client() - c.get('/') - c.get('/test_with_categories/') + client = app.test_client() + client.get('/') + client.get('/test_with_categories/') - c = app.test_client() - c.get('/') - c.get('/test_filter/') + client = app.test_client() + client.get('/') + client.get('/test_filter/') - c = app.test_client() - c.get('/') - c.get('/test_filters/') + client = app.test_client() + client.get('/') + client.get('/test_filters/') - c = app.test_client() - c.get('/') - c.get('/test_filters_without_returning_categories/') + client = app.test_client() + client.get('/') + client.get('/test_filters_without_returning_categories/') -def test_request_processing(): - app = flask.Flask(__name__) +def test_request_processing(app, client): evts = [] @app.before_request @@ -691,14 +650,14 @@ def test_request_processing(): assert 'before' in evts assert 'after' not in evts return 'request' + assert 'after' not in evts - rv = app.test_client().get('/').data + rv = client.get('/').data assert 'after' in evts assert rv == b'request|after' -def test_request_preprocessing_early_return(): - app = flask.Flask(__name__) +def test_request_preprocessing_early_return(app, client): evts = [] @app.before_request @@ -720,31 +679,28 @@ def test_request_preprocessing_early_return(): evts.append('index') return "damnit" - rv = app.test_client().get('/').data.strip() + rv = client.get('/').data.strip() assert rv == b'hello' assert evts == [1, 2] -def test_after_request_processing(): - app = flask.Flask(__name__) - app.testing = True - +def test_after_request_processing(app, client): @app.route('/') def index(): @flask.after_this_request def foo(response): response.headers['X-Foo'] = 'a header' return response + return 'Test' - c = app.test_client() - resp = c.get('/') + + resp = client.get('/') assert resp.status_code == 200 assert resp.headers['X-Foo'] == 'a header' -def test_teardown_request_handler(): +def test_teardown_request_handler(app, client): called = [] - app = flask.Flask(__name__) @app.teardown_request def teardown_request(exc): @@ -754,16 +710,15 @@ def test_teardown_request_handler(): @app.route('/') def root(): return "Response" - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.status_code == 200 assert b'Response' in rv.data assert len(called) == 1 -def test_teardown_request_handler_debug_mode(): +def test_teardown_request_handler_debug_mode(app, client): called = [] - app = flask.Flask(__name__) - app.testing = True @app.teardown_request def teardown_request(exc): @@ -773,16 +728,17 @@ def test_teardown_request_handler_debug_mode(): @app.route('/') def root(): return "Response" - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.status_code == 200 assert b'Response' in rv.data assert len(called) == 1 -def test_teardown_request_handler_error(): +def test_teardown_request_handler_error(app, client): called = [] - app = flask.Flask(__name__) app.config['LOGGER_HANDLER_POLICY'] = 'never' + app.testing = False @app.teardown_request def teardown_request1(exc): @@ -811,15 +767,15 @@ def test_teardown_request_handler_error(): @app.route('/') def fails(): 1 // 0 - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.status_code == 500 assert b'Internal Server Error' in rv.data assert len(called) == 2 -def test_before_after_request_order(): +def test_before_after_request_order(app, client): called = [] - app = flask.Flask(__name__) @app.before_request def before1(): @@ -850,14 +806,15 @@ def test_before_after_request_order(): @app.route('/') def index(): return '42' - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.data == b'42' assert called == [1, 2, 3, 4, 5, 6] -def test_error_handling(): - app = flask.Flask(__name__) +def test_error_handling(app, client): app.config['LOGGER_HANDLER_POLICY'] = 'never' + app.testing = False @app.errorhandler(404) def not_found(e): @@ -882,21 +839,21 @@ def test_error_handling(): @app.route('/forbidden') def error2(): flask.abort(403) - c = app.test_client() - rv = c.get('/') + + rv = client.get('/') assert rv.status_code == 404 assert rv.data == b'not found' - rv = c.get('/error') + rv = client.get('/error') assert rv.status_code == 500 assert b'internal server error' == rv.data - rv = c.get('/forbidden') + rv = client.get('/forbidden') assert rv.status_code == 403 assert b'forbidden' == rv.data -def test_error_handling_processing(): - app = flask.Flask(__name__) +def test_error_handling_processing(app, client): app.config['LOGGER_HANDLER_POLICY'] = 'never' + app.testing = False @app.errorhandler(500) def internal_server_error(e): @@ -911,32 +868,28 @@ def test_error_handling_processing(): resp.mimetype = 'text/x-special' return resp - with app.test_client() as c: - resp = c.get('/') - assert resp.mimetype == 'text/x-special' - assert resp.data == b'internal server error' + resp = client.get('/') + assert resp.mimetype == 'text/x-special' + assert resp.data == b'internal server error' -def test_baseexception_error_handling(): - app = flask.Flask(__name__) +def test_baseexception_error_handling(app, client): app.config['LOGGER_HANDLER_POLICY'] = 'never' + app.testing = False @app.route('/') def broken_func(): raise KeyboardInterrupt() - with app.test_client() as c: - with pytest.raises(KeyboardInterrupt): - c.get('/') + with pytest.raises(KeyboardInterrupt): + client.get('/') ctx = flask._request_ctx_stack.top assert ctx.preserved assert type(ctx._preserved_exc) is KeyboardInterrupt -def test_before_request_and_routing_errors(): - app = flask.Flask(__name__) - +def test_before_request_and_routing_errors(app, client): @app.before_request def attach_something(): flask.g.something = 'value' @@ -944,17 +897,16 @@ def test_before_request_and_routing_errors(): @app.errorhandler(404) def return_something(error): return flask.g.something, 404 - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.status_code == 404 assert rv.data == b'value' -def test_user_error_handling(): +def test_user_error_handling(app, client): class MyException(Exception): pass - app = flask.Flask(__name__) - @app.errorhandler(MyException) def handle_my_exception(e): assert isinstance(e, MyException) @@ -964,16 +916,13 @@ def test_user_error_handling(): def index(): raise MyException() - c = app.test_client() - assert c.get('/').data == b'42' + assert client.get('/').data == b'42' -def test_http_error_subclass_handling(): +def test_http_error_subclass_handling(app, client): class ForbiddenSubclass(Forbidden): pass - app = flask.Flask(__name__) - @app.errorhandler(ForbiddenSubclass) def handle_forbidden_subclass(e): assert isinstance(e, ForbiddenSubclass) @@ -997,19 +946,16 @@ def test_http_error_subclass_handling(): def index3(): raise Forbidden() - c = app.test_client() - assert c.get('/1').data == b'banana' - assert c.get('/2').data == b'apple' - assert c.get('/3').data == b'apple' + assert client.get('/1').data == b'banana' + assert client.get('/2').data == b'apple' + assert client.get('/3').data == b'apple' -def test_trapping_of_bad_request_key_errors(): - app = flask.Flask(__name__) - app.testing = True - +def test_trapping_of_bad_request_key_errors(app): @app.route('/fail') def fail(): flask.request.form['missing_key'] + c = app.test_client() assert c.get('/fail').status_code == 400 @@ -1020,23 +966,19 @@ def test_trapping_of_bad_request_key_errors(): assert e.errisinstance(BadRequest) -def test_trapping_of_all_http_exceptions(): - app = flask.Flask(__name__) - app.testing = True +def test_trapping_of_all_http_exceptions(app, client): app.config['TRAP_HTTP_EXCEPTIONS'] = True @app.route('/fail') def fail(): flask.abort(404) - c = app.test_client() with pytest.raises(NotFound): - c.get('/fail') + client.get('/fail') -def test_enctype_debug_helper(): +def test_enctype_debug_helper(app, client): from flask.debughelpers import DebugFilesKeyError - app = flask.Flask(__name__) app.debug = True @app.route('/fail', methods=['POST']) @@ -1046,7 +988,7 @@ def test_enctype_debug_helper(): # with statement is important because we leave an exception on the # stack otherwise and we want to ensure that this is not the case # to not negatively affect other tests. - with app.test_client() as c: + with client as c: with pytest.raises(DebugFilesKeyError) as e: c.post('/fail', data={'foo': 'index.txt'}) assert 'no file contents were transmitted' in str(e.value) @@ -1230,7 +1172,7 @@ def test_jsonify_no_prettyprint(): "submsg": "W00t" }, "msg2": "foobar" - } + } rv = flask.make_response( flask.jsonify(uncompressed_msg), 200) @@ -1241,8 +1183,8 @@ def test_jsonify_prettyprint(): app = flask.Flask(__name__) app.config.update({"JSONIFY_PRETTYPRINT_REGULAR": True}) with app.test_request_context(): - compressed_msg = {"msg":{"submsg":"W00t"},"msg2":"foobar"} - pretty_response =\ + compressed_msg = {"msg": {"submsg": "W00t"}, "msg2": "foobar"} + pretty_response = \ b'{\n "msg": {\n "submsg": "W00t"\n }, \n "msg2": "foobar"\n}\n' rv = flask.make_response( @@ -1276,10 +1218,11 @@ def test_url_generation(): @app.route('/hello/', methods=['POST']) def hello(): pass + with app.test_request_context(): assert flask.url_for('hello', name='test x') == '/hello/test%20x' assert flask.url_for('hello', name='test x', _external=True) == \ - 'http://localhost/hello/test%20x' + 'http://localhost/hello/test%20x' def test_build_error_handler(): @@ -1305,6 +1248,7 @@ def test_build_error_handler(): def handler(error, endpoint, values): # Just a test. return '/test_handler/' + app.url_build_error_handlers.append(handler) with app.test_request_context(): assert flask.url_for('spam') == '/test_handler/' @@ -1316,6 +1260,7 @@ def test_build_error_handler_reraise(): # Test a custom handler which reraises the BuildError def handler_raises_build_error(error, endpoint, values): raise error + app.url_build_error_handlers.append(handler_raises_build_error) with app.test_request_context(): @@ -1343,19 +1288,20 @@ def test_custom_converters(): from werkzeug.routing import BaseConverter class ListConverter(BaseConverter): - def to_python(self, value): return value.split(',') def to_url(self, value): base_to_url = super(ListConverter, self).to_url return ','.join(base_to_url(x) for x in value) + app = flask.Flask(__name__) app.url_map.converters['list'] = ListConverter @app.route('/') def index(args): return '|'.join(args) + c = app.test_client() assert c.get('/1,2,3').data == b'1|2|3' @@ -1368,7 +1314,7 @@ def test_static_files(): assert rv.data.strip() == b'

      Hello World!

      ' with app.test_request_context(): assert flask.url_for('static', filename='index.html') == \ - '/static/index.html' + '/static/index.html' rv.close() @@ -1403,8 +1349,8 @@ def test_static_route_with_host_matching(): assert rv.status_code == 200 rv.close() with app.test_request_context(): - rv = flask.url_for('static', filename='index.html', _external=True) - assert rv == 'http://example.com/static/index.html' + rv = flask.url_for('static', filename='index.html', _external=True) + assert rv == 'http://example.com/static/index.html' # Providing static_host without host_matching=True should error. with pytest.raises(Exception): flask.Flask(__name__, static_host='example.com') @@ -1485,6 +1431,7 @@ def test_exception_propagation(): @app.route('/') def index(): 1 // 0 + c = app.test_client() if config_key is not None: app.config[config_key] = True @@ -1550,7 +1497,7 @@ def test_url_processors(): @app.url_defaults def add_language_code(endpoint, values): if flask.g.lang_code is not None and \ - app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): values.setdefault('lang_code', flask.g.lang_code) @app.url_value_preprocessor @@ -1622,6 +1569,7 @@ def test_debug_mode_complains_after_first_request(): @app.route('/') def index(): return 'Awesome' + assert not app.got_first_request assert app.test_client().get('/').data == b'Awesome' with pytest.raises(AssertionError) as e: @@ -1635,6 +1583,7 @@ def test_debug_mode_complains_after_first_request(): @app.route('/foo') def working(): return 'Meh' + assert app.test_client().get('/foo').data == b'Meh' assert app.got_first_request @@ -1646,6 +1595,7 @@ def test_before_first_request_functions(): @app.before_first_request def foo(): got.append(42) + c = app.test_client() c.get('/') assert got == [42] @@ -1683,6 +1633,7 @@ def test_routing_redirect_debugging(): @app.route('/foo/', methods=['GET', 'POST']) def foo(): return 'success' + with app.test_client() as c: with pytest.raises(AssertionError) as e: c.post('/foo', data={}) @@ -1747,8 +1698,7 @@ def test_preserve_only_once(): assert flask._app_ctx_stack.top is None -def test_preserve_remembers_exception(): - app = flask.Flask(__name__) +def test_preserve_remembers_exception(app, client): app.debug = True errors = [] @@ -1764,51 +1714,40 @@ def test_preserve_remembers_exception(): def teardown_handler(exc): errors.append(exc) - c = app.test_client() - # After this failure we did not yet call the teardown handler with pytest.raises(ZeroDivisionError): - c.get('/fail') + client.get('/fail') assert errors == [] # But this request triggers it, and it's an error - c.get('/success') + client.get('/success') assert len(errors) == 2 assert isinstance(errors[0], ZeroDivisionError) # At this point another request does nothing. - c.get('/success') + client.get('/success') assert len(errors) == 3 assert errors[1] is None -def test_get_method_on_g(): - app = flask.Flask(__name__) - app.testing = True - - with app.app_context(): - assert flask.g.get('x') is None - assert flask.g.get('x', 11) == 11 - flask.g.x = 42 - assert flask.g.get('x') == 42 - assert flask.g.x == 42 - +def test_get_method_on_g(app_ctx): + assert flask.g.get('x') is None + assert flask.g.get('x', 11) == 11 + flask.g.x = 42 + assert flask.g.get('x') == 42 + assert flask.g.x == 42 -def test_g_iteration_protocol(): - app = flask.Flask(__name__) - app.testing = True - with app.app_context(): - flask.g.foo = 23 - flask.g.bar = 42 - assert 'foo' in flask.g - assert 'foos' not in flask.g - assert sorted(flask.g) == ['bar', 'foo'] +def test_g_iteration_protocol(app_ctx): + flask.g.foo = 23 + flask.g.bar = 42 + assert 'foo' in flask.g + assert 'foos' not in flask.g + assert sorted(flask.g) == ['bar', 'foo'] -def test_subdomain_basic_support(): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' +def test_subdomain_basic_support(app, client): + app.config['SERVER_NAME'] = 'localhost.localdomain' @app.route('/') def normal_index(): @@ -1818,57 +1757,49 @@ def test_subdomain_basic_support(): def test_index(): return 'test index' - c = app.test_client() - rv = c.get('/', 'http://localhost/') + rv = client.get('/', 'http://localhost.localdomain/') assert rv.data == b'normal index' - rv = c.get('/', 'http://test.localhost/') + rv = client.get('/', 'http://test.localhost.localdomain/') assert rv.data == b'test index' -def test_subdomain_matching(): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' +def test_subdomain_matching(app, client): + app.config['SERVER_NAME'] = 'localhost.localdomain' @app.route('/', subdomain='') def index(user): return 'index for %s' % user - c = app.test_client() - rv = c.get('/', 'http://mitsuhiko.localhost/') + rv = client.get('/', 'http://mitsuhiko.localhost.localdomain/') assert rv.data == b'index for mitsuhiko' -def test_subdomain_matching_with_ports(): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost:3000' +def test_subdomain_matching_with_ports(app, client): + app.config['SERVER_NAME'] = 'localhost.localdomain:3000' @app.route('/', subdomain='') def index(user): return 'index for %s' % user - c = app.test_client() - rv = c.get('/', 'http://mitsuhiko.localhost:3000/') + rv = client.get('/', 'http://mitsuhiko.localhost.localdomain:3000/') assert rv.data == b'index for mitsuhiko' -def test_multi_route_rules(): - app = flask.Flask(__name__) - +def test_multi_route_rules(app, client): @app.route('/') @app.route('//') def index(test='a'): return test - rv = app.test_client().open('/') + rv = client.open('/') assert rv.data == b'a' - rv = app.test_client().open('/b/') + rv = client.open('/b/') assert rv.data == b'b' -def test_multi_route_class_views(): +def test_multi_route_class_views(app, client): class View(object): - def __init__(self, app): app.add_url_rule('/', 'index', self.index) app.add_url_rule('//', 'index', self.index) @@ -1876,35 +1807,32 @@ def test_multi_route_class_views(): def index(self, test='a'): return test - app = flask.Flask(__name__) _ = View(app) - rv = app.test_client().open('/') + rv = client.open('/') assert rv.data == b'a' - rv = app.test_client().open('/b/') + rv = client.open('/b/') assert rv.data == b'b' -def test_run_defaults(monkeypatch): +def test_run_defaults(monkeypatch, app): rv = {} # Mocks werkzeug.serving.run_simple method def run_simple_mock(*args, **kwargs): rv['result'] = 'running...' - app = flask.Flask(__name__) monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) app.run() assert rv['result'] == 'running...' -def test_run_server_port(monkeypatch): +def test_run_server_port(monkeypatch, app): rv = {} # Mocks werkzeug.serving.run_simple method def run_simple_mock(hostname, port, application, *args, **kwargs): rv['result'] = 'running on %s:%s ...' % (hostname, port) - app = flask.Flask(__name__) monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) hostname, port = 'localhost', 8000 app.run(hostname, port, debug=True) @@ -1912,17 +1840,16 @@ def test_run_server_port(monkeypatch): @pytest.mark.parametrize('host,port,expect_host,expect_port', ( - (None, None, 'pocoo.org', 8080), - ('localhost', None, 'localhost', 8080), - (None, 80, 'pocoo.org', 80), - ('localhost', 80, 'localhost', 80), + (None, None, 'pocoo.org', 8080), + ('localhost', None, 'localhost', 8080), + (None, 80, 'pocoo.org', 80), + ('localhost', 80, 'localhost', 80), )) -def test_run_from_config(monkeypatch, host, port, expect_host, expect_port): +def test_run_from_config(monkeypatch, host, port, expect_host, expect_port, app): def run_simple_mock(hostname, port, *args, **kwargs): assert hostname == expect_host assert port == expect_port monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) - app = flask.Flask(__name__) app.config['SERVER_NAME'] = 'pocoo.org:8080' app.run(host, port) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index 5c5119c0..434fca37 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -18,7 +18,7 @@ from werkzeug.http import parse_cache_control_header from jinja2 import TemplateNotFound -def test_blueprint_specific_error_handling(): +def test_blueprint_specific_error_handling(app, client): frontend = flask.Blueprint('frontend', __name__) backend = flask.Blueprint('backend', __name__) sideend = flask.Blueprint('sideend', __name__) @@ -43,7 +43,6 @@ def test_blueprint_specific_error_handling(): def sideend_no(): flask.abort(403) - app = flask.Flask(__name__) app.register_blueprint(frontend) app.register_blueprint(backend) app.register_blueprint(sideend) @@ -52,15 +51,15 @@ def test_blueprint_specific_error_handling(): def app_forbidden(e): return 'application itself says no', 403 - c = app.test_client() + assert client.get('/frontend-no').data == b'frontend says no' + assert client.get('/backend-no').data == b'backend says no' + assert client.get('/what-is-a-sideend').data == b'application itself says no' - assert c.get('/frontend-no').data == b'frontend says no' - assert c.get('/backend-no').data == b'backend says no' - assert c.get('/what-is-a-sideend').data == b'application itself says no' -def test_blueprint_specific_user_error_handling(): +def test_blueprint_specific_user_error_handling(app, client): class MyDecoratorException(Exception): pass + class MyFunctionException(Exception): pass @@ -74,32 +73,30 @@ def test_blueprint_specific_user_error_handling(): def my_function_exception_handler(e): assert isinstance(e, MyFunctionException) return 'bam' + blue.register_error_handler(MyFunctionException, my_function_exception_handler) @blue.route('/decorator') def blue_deco_test(): raise MyDecoratorException() + @blue.route('/function') def blue_func_test(): raise MyFunctionException() - app = flask.Flask(__name__) app.register_blueprint(blue) - c = app.test_client() + assert client.get('/decorator').data == b'boom' + assert client.get('/function').data == b'bam' - assert c.get('/decorator').data == b'boom' - assert c.get('/function').data == b'bam' -def test_blueprint_app_error_handling(): +def test_blueprint_app_error_handling(app, client): errors = flask.Blueprint('errors', __name__) @errors.app_errorhandler(403) def forbidden_handler(e): return 'you shall not pass', 403 - app = flask.Flask(__name__) - @app.route('/forbidden') def app_forbidden(): flask.abort(403) @@ -113,12 +110,11 @@ def test_blueprint_app_error_handling(): app.register_blueprint(errors) app.register_blueprint(forbidden_bp) - c = app.test_client() + assert client.get('/forbidden').data == b'you shall not pass' + assert client.get('/nope').data == b'you shall not pass' - assert c.get('/forbidden').data == b'you shall not pass' - assert c.get('/nope').data == b'you shall not pass' -def test_blueprint_url_definitions(): +def test_blueprint_url_definitions(app, client): bp = flask.Blueprint('test', __name__) @bp.route('/foo', defaults={'baz': 42}) @@ -129,17 +125,16 @@ def test_blueprint_url_definitions(): def bar(bar): return text_type(bar) - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) - c = app.test_client() - assert c.get('/1/foo').data == b'23/42' - assert c.get('/2/foo').data == b'19/42' - assert c.get('/1/bar').data == b'23' - assert c.get('/2/bar').data == b'19' + assert client.get('/1/foo').data == b'23/42' + assert client.get('/2/foo').data == b'19/42' + assert client.get('/1/bar').data == b'23' + assert client.get('/2/bar').data == b'19' -def test_blueprint_url_processors(): + +def test_blueprint_url_processors(app, client): bp = flask.Blueprint('frontend', __name__, url_prefix='/') @bp.url_defaults @@ -158,28 +153,26 @@ def test_blueprint_url_processors(): def about(): return flask.url_for('.index') - app = flask.Flask(__name__) app.register_blueprint(bp) - c = app.test_client() + assert client.get('/de/').data == b'/de/about' + assert client.get('/de/about').data == b'/de/' - assert c.get('/de/').data == b'/de/about' - assert c.get('/de/about').data == b'/de/' def test_templates_and_static(test_apps): from blueprintapp import app - c = app.test_client() + client = app.test_client() - rv = c.get('/') + rv = client.get('/') assert rv.data == b'Hello from the Frontend' - rv = c.get('/admin/') + rv = client.get('/admin/') assert rv.data == b'Hello from the Admin' - rv = c.get('/admin/index2') + rv = client.get('/admin/index2') assert rv.data == b'Hello from the Admin' - rv = c.get('/admin/static/test.txt') + rv = client.get('/admin/static/test.txt') assert rv.data.strip() == b'Admin File' rv.close() - rv = c.get('/admin/static/css/test.css') + rv = client.get('/admin/static/css/test.css') assert rv.data.strip() == b'/* nested file */' rv.close() @@ -190,7 +183,7 @@ def test_templates_and_static(test_apps): if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age: expected_max_age = 7200 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age - rv = c.get('/admin/static/css/test.css') + rv = client.get('/admin/static/css/test.css') cc = parse_cache_control_header(rv.headers['Cache-Control']) assert cc.max_age == expected_max_age rv.close() @@ -208,8 +201,10 @@ def test_templates_and_static(test_apps): with flask.Flask(__name__).test_request_context(): assert flask.render_template('nested/nested.txt') == 'I\'m nested' + def test_default_static_cache_timeout(): app = flask.Flask(__name__) + class MyBlueprint(flask.Blueprint): def get_send_file_max_age(self, filename): return 100 @@ -232,12 +227,14 @@ def test_default_static_cache_timeout(): finally: app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default + def test_templates_list(test_apps): from blueprintapp import app templates = sorted(app.jinja_env.list_templates()) assert templates == ['admin/index.html', 'frontend/index.html'] -def test_dotted_names(): + +def test_dotted_names(app, client): frontend = flask.Blueprint('myapp.frontend', __name__) backend = flask.Blueprint('myapp.backend', __name__) @@ -253,18 +250,15 @@ def test_dotted_names(): def backend_index(): return flask.url_for('myapp.frontend.frontend_index') - app = flask.Flask(__name__) app.register_blueprint(frontend) app.register_blueprint(backend) - c = app.test_client() - assert c.get('/fe').data.strip() == b'/be' - assert c.get('/fe2').data.strip() == b'/fe' - assert c.get('/be').data.strip() == b'/fe' + assert client.get('/fe').data.strip() == b'/be' + assert client.get('/fe2').data.strip() == b'/fe' + assert client.get('/be').data.strip() == b'/fe' -def test_dotted_names_from_app(): - app = flask.Flask(__name__) - app.testing = True + +def test_dotted_names_from_app(app, client): test = flask.Blueprint('test', __name__) @app.route('/') @@ -277,11 +271,11 @@ def test_dotted_names_from_app(): app.register_blueprint(test) - with app.test_client() as c: - rv = c.get('/') - assert rv.data == b'/test/' + rv = client.get('/') + assert rv.data == b'/test/' -def test_empty_url_defaults(): + +def test_empty_url_defaults(app, client): bp = flask.Blueprint('bp', __name__) @bp.route('/', defaults={'page': 1}) @@ -289,15 +283,13 @@ def test_empty_url_defaults(): def something(page): return str(page) - app = flask.Flask(__name__) app.register_blueprint(bp) - c = app.test_client() - assert c.get('/').data == b'1' - assert c.get('/page/2').data == b'2' + assert client.get('/').data == b'1' + assert client.get('/page/2').data == b'2' -def test_route_decorator_custom_endpoint(): +def test_route_decorator_custom_endpoint(app, client): bp = flask.Blueprint('bp', __name__) @bp.route('/foo') @@ -316,21 +308,20 @@ def test_route_decorator_custom_endpoint(): def bar_foo(): return flask.request.endpoint - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') @app.route('/') def index(): return flask.request.endpoint - c = app.test_client() - assert c.get('/').data == b'index' - assert c.get('/py/foo').data == b'bp.foo' - assert c.get('/py/bar').data == b'bp.bar' - assert c.get('/py/bar/123').data == b'bp.123' - assert c.get('/py/bar/foo').data == b'bp.bar_foo' + assert client.get('/').data == b'index' + assert client.get('/py/foo').data == b'bp.foo' + assert client.get('/py/bar').data == b'bp.bar' + assert client.get('/py/bar/123').data == b'bp.123' + assert client.get('/py/bar/foo').data == b'bp.bar_foo' + -def test_route_decorator_custom_endpoint_with_dots(): +def test_route_decorator_custom_endpoint_with_dots(app, client): bp = flask.Blueprint('bp', __name__) @bp.route('/foo') @@ -371,21 +362,18 @@ def test_route_decorator_custom_endpoint_with_dots(): lambda: None ) - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - c = app.test_client() - assert c.get('/py/foo').data == b'bp.foo' + assert client.get('/py/foo').data == b'bp.foo' # The rule's didn't actually made it through - rv = c.get('/py/bar') + rv = client.get('/py/bar') assert rv.status_code == 404 - rv = c.get('/py/bar/123') + rv = client.get('/py/bar/123') assert rv.status_code == 404 -def test_endpoint_decorator(): +def test_endpoint_decorator(app, client): from werkzeug.routing import Rule - app = flask.Flask(__name__) app.url_map.add(Rule('/foo', endpoint='bar')) bp = flask.Blueprint('bp', __name__) @@ -396,229 +384,282 @@ def test_endpoint_decorator(): app.register_blueprint(bp, url_prefix='/bp_prefix') - c = app.test_client() - assert c.get('/foo').data == b'bar' - assert c.get('/bp_prefix/bar').status_code == 404 + assert client.get('/foo').data == b'bar' + assert client.get('/bp_prefix/bar').status_code == 404 -def test_template_filter(): +def test_template_filter(app): bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter() def my_reverse(s): return s[::-1] - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') assert 'my_reverse' in app.jinja_env.filters.keys() assert app.jinja_env.filters['my_reverse'] == my_reverse assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' -def test_add_template_filter(): + +def test_add_template_filter(app): bp = flask.Blueprint('bp', __name__) + def my_reverse(s): return s[::-1] + bp.add_app_template_filter(my_reverse) - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') assert 'my_reverse' in app.jinja_env.filters.keys() assert app.jinja_env.filters['my_reverse'] == my_reverse assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' -def test_template_filter_with_name(): + +def test_template_filter_with_name(app): bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter('strrev') def my_reverse(s): return s[::-1] - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') assert 'strrev' in app.jinja_env.filters.keys() assert app.jinja_env.filters['strrev'] == my_reverse assert app.jinja_env.filters['strrev']('abcd') == 'dcba' -def test_add_template_filter_with_name(): + +def test_add_template_filter_with_name(app): bp = flask.Blueprint('bp', __name__) + def my_reverse(s): return s[::-1] + bp.add_app_template_filter(my_reverse, 'strrev') - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') assert 'strrev' in app.jinja_env.filters.keys() assert app.jinja_env.filters['strrev'] == my_reverse assert app.jinja_env.filters['strrev']('abcd') == 'dcba' -def test_template_filter_with_template(): + +def test_template_filter_with_template(app, client): bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter() def super_reverse(s): return s[::-1] - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.data == b'dcba' -def test_template_filter_after_route_with_template(): - app = flask.Flask(__name__) + +def test_template_filter_after_route_with_template(app, client): @app.route('/') def index(): return flask.render_template('template_filter.html', value='abcd') + bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter() def super_reverse(s): return s[::-1] + app.register_blueprint(bp, url_prefix='/py') - rv = app.test_client().get('/') + rv = client.get('/') assert rv.data == b'dcba' -def test_add_template_filter_with_template(): + +def test_add_template_filter_with_template(app, client): bp = flask.Blueprint('bp', __name__) + def super_reverse(s): return s[::-1] + bp.add_app_template_filter(super_reverse) - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.data == b'dcba' -def test_template_filter_with_name_and_template(): + +def test_template_filter_with_name_and_template(app, client): bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter('super_reverse') def my_reverse(s): return s[::-1] - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.data == b'dcba' -def test_add_template_filter_with_name_and_template(): + +def test_add_template_filter_with_name_and_template(app, client): bp = flask.Blueprint('bp', __name__) + def my_reverse(s): return s[::-1] + bp.add_app_template_filter(my_reverse, 'super_reverse') - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') + + rv = client.get('/') assert rv.data == b'dcba' -def test_template_test(): + +def test_template_test(app): bp = flask.Blueprint('bp', __name__) + @bp.app_template_test() def is_boolean(value): return isinstance(value, bool) - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') assert 'is_boolean' in app.jinja_env.tests.keys() assert app.jinja_env.tests['is_boolean'] == is_boolean assert app.jinja_env.tests['is_boolean'](False) -def test_add_template_test(): + +def test_add_template_test(app): bp = flask.Blueprint('bp', __name__) + def is_boolean(value): return isinstance(value, bool) + bp.add_app_template_test(is_boolean) - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') assert 'is_boolean' in app.jinja_env.tests.keys() assert app.jinja_env.tests['is_boolean'] == is_boolean assert app.jinja_env.tests['is_boolean'](False) -def test_template_test_with_name(): + +def test_template_test_with_name(app): bp = flask.Blueprint('bp', __name__) + @bp.app_template_test('boolean') def is_boolean(value): return isinstance(value, bool) - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') assert 'boolean' in app.jinja_env.tests.keys() assert app.jinja_env.tests['boolean'] == is_boolean assert app.jinja_env.tests['boolean'](False) -def test_add_template_test_with_name(): + +def test_add_template_test_with_name(app): bp = flask.Blueprint('bp', __name__) + def is_boolean(value): return isinstance(value, bool) + bp.add_app_template_test(is_boolean, 'boolean') - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') assert 'boolean' in app.jinja_env.tests.keys() assert app.jinja_env.tests['boolean'] == is_boolean assert app.jinja_env.tests['boolean'](False) -def test_template_test_with_template(): + +def test_template_test_with_template(app, client): bp = flask.Blueprint('bp', __name__) + @bp.app_template_test() def boolean(value): return isinstance(value, bool) - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_test.html', value=False) - rv = app.test_client().get('/') + + rv = client.get('/') assert b'Success!' in rv.data -def test_template_test_after_route_with_template(): - app = flask.Flask(__name__) + +def test_template_test_after_route_with_template(app, client): @app.route('/') def index(): return flask.render_template('template_test.html', value=False) + bp = flask.Blueprint('bp', __name__) + @bp.app_template_test() def boolean(value): return isinstance(value, bool) + app.register_blueprint(bp, url_prefix='/py') - rv = app.test_client().get('/') + rv = client.get('/') assert b'Success!' in rv.data -def test_add_template_test_with_template(): + +def test_add_template_test_with_template(app, client): bp = flask.Blueprint('bp', __name__) + def boolean(value): return isinstance(value, bool) + bp.add_app_template_test(boolean) - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_test.html', value=False) - rv = app.test_client().get('/') + + rv = client.get('/') assert b'Success!' in rv.data -def test_template_test_with_name_and_template(): + +def test_template_test_with_name_and_template(app, client): bp = flask.Blueprint('bp', __name__) + @bp.app_template_test('boolean') def is_boolean(value): return isinstance(value, bool) - app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_test.html', value=False) - rv = app.test_client().get('/') + + rv = client.get('/') assert b'Success!' in rv.data -def test_add_template_test_with_name_and_template(): + +def test_add_template_test_with_name_and_template(app, client): bp = flask.Blueprint('bp', __name__) + def is_boolean(value): return isinstance(value, bool) + bp.add_app_template_test(is_boolean, 'boolean') - app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') + @app.route('/') def index(): return flask.render_template('template_test.html', value=False) - rv = app.test_client().get('/') + + rv = client.get('/') assert b'Success!' in rv.data + def test_context_processing(): app = flask.Flask(__name__) answer_bp = flask.Blueprint('answer_bp', __name__) @@ -661,12 +702,15 @@ def test_context_processing(): assert b'42' in answer_page_bytes assert b'43' in answer_page_bytes + def test_template_global(): app = flask.Flask(__name__) bp = flask.Blueprint('bp', __name__) + @bp.app_template_global() def get_answer(): return 42 + # Make sure the function is not in the jinja_env already assert 'get_answer' not in app.jinja_env.globals.keys() app.register_blueprint(bp) diff --git a/tests/test_deprecations.py b/tests/test_deprecations.py index 666f7d56..7383604e 100644 --- a/tests/test_deprecations.py +++ b/tests/test_deprecations.py @@ -15,10 +15,8 @@ import flask class TestRequestDeprecation(object): - - def test_request_json(self, recwarn): + def test_request_json(self, recwarn, app, client): """Request.json is deprecated""" - app = flask.Flask(__name__) app.testing = True @app.route('/', methods=['POST']) @@ -27,13 +25,11 @@ class TestRequestDeprecation(object): print(flask.request.json) return 'OK' - c = app.test_client() - c.post('/', data='{"spam": 42}', content_type='application/json') + client.post('/', data='{"spam": 42}', content_type='application/json') recwarn.pop(DeprecationWarning) - def test_request_module(self, recwarn): + def test_request_module(self, recwarn, app, client): """Request.module is deprecated""" - app = flask.Flask(__name__) app.testing = True @app.route('/') @@ -41,6 +37,5 @@ class TestRequestDeprecation(object): assert flask.request.module is None return 'OK' - c = app.test_client() - c.get('/') + client.get('/') recwarn.pop(DeprecationWarning) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 5af95f18..4259d2d9 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -35,240 +35,226 @@ def has_encoding(name): class TestJSON(object): - - def test_ignore_cached_json(self): - app = flask.Flask(__name__) + def test_ignore_cached_json(self, app): with app.test_request_context('/', method='POST', data='malformed', content_type='application/json'): assert flask.request.get_json(silent=True, cache=True) is None with pytest.raises(BadRequest): flask.request.get_json(silent=False, cache=False) - def test_post_empty_json_adds_exception_to_response_content_in_debug(self): - app = flask.Flask(__name__) + def test_post_empty_json_adds_exception_to_response_content_in_debug(self, app, client): app.config['DEBUG'] = True + @app.route('/json', methods=['POST']) def post_json(): flask.request.get_json() return None - c = app.test_client() - rv = c.post('/json', data=None, content_type='application/json') + + rv = client.post('/json', data=None, content_type='application/json') assert rv.status_code == 400 assert b'Failed to decode JSON object' in rv.data - def test_post_empty_json_wont_add_exception_to_response_if_no_debug(self): - app = flask.Flask(__name__) + def test_post_empty_json_wont_add_exception_to_response_if_no_debug(self, app, client): app.config['DEBUG'] = False + @app.route('/json', methods=['POST']) def post_json(): flask.request.get_json() return None - c = app.test_client() - rv = c.post('/json', data=None, content_type='application/json') + + rv = client.post('/json', data=None, content_type='application/json') assert rv.status_code == 400 assert b'Failed to decode JSON object' not in rv.data - def test_json_bad_requests(self): - app = flask.Flask(__name__) + def test_json_bad_requests(self, app, client): + @app.route('/json', methods=['POST']) def return_json(): return flask.jsonify(foo=text_type(flask.request.get_json())) - c = app.test_client() - rv = c.post('/json', data='malformed', content_type='application/json') + + rv = client.post('/json', data='malformed', content_type='application/json') assert rv.status_code == 400 - def test_json_custom_mimetypes(self): - app = flask.Flask(__name__) + def test_json_custom_mimetypes(self, app, client): + @app.route('/json', methods=['POST']) def return_json(): return flask.request.get_json() - c = app.test_client() - rv = c.post('/json', data='"foo"', content_type='application/x+json') + + rv = client.post('/json', data='"foo"', content_type='application/x+json') assert rv.data == b'foo' - def test_json_body_encoding(self): - app = flask.Flask(__name__) + def test_json_body_encoding(self, app, client): app.testing = True + @app.route('/') def index(): return flask.request.get_json() - c = app.test_client() - resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), - content_type='application/json; charset=iso-8859-15') + resp = client.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), + content_type='application/json; charset=iso-8859-15') assert resp.data == u'Hällo Wörld'.encode('utf-8') - def test_json_as_unicode(self): - app = flask.Flask(__name__) + @pytest.mark.parametrize('test_value,expected', [(True, '"\\u2603"'), (False, u'"\u2603"')]) + def test_json_as_unicode(self, test_value, expected, app, app_ctx): - app.config['JSON_AS_ASCII'] = True - with app.app_context(): - rv = flask.json.dumps(u'\N{SNOWMAN}') - assert rv == '"\\u2603"' + app.config['JSON_AS_ASCII'] = test_value + rv = flask.json.dumps(u'\N{SNOWMAN}') + assert rv == expected - app.config['JSON_AS_ASCII'] = False - with app.app_context(): - rv = flask.json.dumps(u'\N{SNOWMAN}') - assert rv == u'"\u2603"' - - def test_json_dump_to_file(self): - app = flask.Flask(__name__) + def test_json_dump_to_file(self, app, app_ctx): test_data = {'name': 'Flask'} out = StringIO() - with app.app_context(): - flask.json.dump(test_data, out) - out.seek(0) - rv = flask.json.load(out) - assert rv == test_data + flask.json.dump(test_data, out) + out.seek(0) + rv = flask.json.load(out) + assert rv == test_data @pytest.mark.parametrize('test_value', [0, -1, 1, 23, 3.14, 's', "longer string", True, False, None]) - def test_jsonify_basic_types(self, test_value): + def test_jsonify_basic_types(self, test_value, app, client): """Test jsonify with basic types.""" - app = flask.Flask(__name__) - c = app.test_client() url = '/jsonify_basic_types' app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x)) - rv = c.get(url) + rv = client.get(url) assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data) == test_value - def test_jsonify_dicts(self): + def test_jsonify_dicts(self, app, client): """Test jsonify with dicts and kwargs unpacking.""" - d = dict( - a=0, b=23, c=3.14, d='t', e='Hi', f=True, g=False, - h=['test list', 10, False], - i={'test':'dict'} - ) - app = flask.Flask(__name__) + d = {'a': 0, 'b': 23, 'c': 3.14, 'd': 't', + 'e': 'Hi', 'f': True, 'g': False, + 'h': ['test list', 10, False], + 'i': {'test': 'dict'}} + @app.route('/kw') def return_kwargs(): return flask.jsonify(**d) + @app.route('/dict') def return_dict(): return flask.jsonify(d) - c = app.test_client() + for url in '/kw', '/dict': - rv = c.get(url) + rv = client.get(url) assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data) == d - def test_jsonify_arrays(self): + def test_jsonify_arrays(self, app, client): """Test jsonify of lists and args unpacking.""" l = [ 0, 42, 3.14, 't', 'hello', True, False, ['test list', 2, False], - {'test':'dict'} + {'test': 'dict'} ] - app = flask.Flask(__name__) + @app.route('/args_unpack') def return_args_unpack(): return flask.jsonify(*l) + @app.route('/array') def return_array(): return flask.jsonify(l) - c = app.test_client() + for url in '/args_unpack', '/array': - rv = c.get(url) + rv = client.get(url) assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data) == l - def test_jsonify_date_types(self): + def test_jsonify_date_types(self, app, client): """Test jsonify with datetime.date and datetime.datetime types.""" test_dates = ( datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5) ) - app = flask.Flask(__name__) - c = app.test_client() for i, d in enumerate(test_dates): url = '/datetest{0}'.format(i) app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val)) - rv = c.get(url) + rv = client.get(url) assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) - def test_jsonify_uuid_types(self): + def test_jsonify_uuid_types(self, app, client): """Test jsonify with uuid.UUID types""" test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF' * 4) - app = flask.Flask(__name__) url = '/uuid_test' app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) - c = app.test_client() - rv = c.get(url) + rv = client.get(url) rv_x = flask.json.loads(rv.data)['x'] assert rv_x == str(test_uuid) rv_uuid = uuid.UUID(rv_x) assert rv_uuid == test_uuid - def test_json_attr(self): - app = flask.Flask(__name__) + def test_json_attr(self, app, client): + @app.route('/add', methods=['POST']) def add(): json = flask.request.get_json() return text_type(json['a'] + json['b']) - c = app.test_client() - rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), - content_type='application/json') + + rv = client.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), + content_type='application/json') assert rv.data == b'3' - def test_template_escaping(self): - app = flask.Flask(__name__) + def test_template_escaping(self, app, req_ctx): render = flask.render_template_string - with app.test_request_context(): - rv = flask.json.htmlsafe_dumps('') - assert rv == u'"\\u003c/script\\u003e"' - assert type(rv) == text_type - rv = render('{{ ""|tojson }}') - assert rv == '"\\u003c/script\\u003e"' - rv = render('{{ "<\0/script>"|tojson }}') - assert rv == '"\\u003c\\u0000/script\\u003e"' - rv = render('{{ "