From 671c67aac96ae2cdf9c554ba4e52c167862be6fe Mon Sep 17 00:00:00 2001 From: Mihir Singh Date: Wed, 16 Apr 2014 17:38:41 -0400 Subject: [PATCH 0001/1888] Append a 'Vary: Cookie' header to the response when the session has been accessed --- flask/sessions.py | 56 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 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 + if session.accessed: - # 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 + 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 0002/1888] 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 f05aeea6dd525d735b5e008aa176b1b839b08cca Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Sat, 25 Oct 2014 15:53:09 -0700 Subject: [PATCH 0003/1888] Correct the order of suggested syntax for extension imports According to https://github.com/mitsuhiko/flask/issues/1092#issuecomment-47118613 and https://github.com/mitsuhiko/flask/pull/1085#issuecomment-45466907 , the correct order to attempt to import extensions should be flask_foo, then flask.ext.foo, then flaskext_foo. --- docs/extensiondev.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 918187fe..e0c8f5f8 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -410,8 +410,8 @@ 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 extensions should urge users to import from ``flask.ext.foo`` -instead of ``flask_foo`` or ``flaskext_foo`` so that extensions can +Flask extensions should urge users to import from ``flask_foo`` +instead of ``flask.ext.foo`` or ``flaskext_foo`` so that extensions can transition to the new package name without affecting users. From 063a8da4f8b9237f425ade907ce97339eff52f56 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 26 Oct 2014 01:09:56 +0200 Subject: [PATCH 0004/1888] Add changelog for #1218 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 284c95d0..0e72642a 100644 --- a/CHANGES +++ b/CHANGES @@ -66,6 +66,8 @@ 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``). +- Updated extension dev guidelines. + Version 0.10.2 -------------- From 43d6b8a5fc5f4efd9dd1ce8c73a0c15979defd22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 26 Apr 2014 02:01:12 +0200 Subject: [PATCH 0005/1888] Drop Extension==dev requirement pip doesn't install links included in the description of projects anymore. Therefore ==dev install doesn't work anymore. --- docs/extensiondev.rst | 66 ++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index e0c8f5f8..395227ec 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -355,43 +355,39 @@ new releases. These approved extensions are listed on the `Flask Extension Registry`_ and marked appropriately. If you want your own extension to be approved you have to follow these guidelines: -0. An approved Flask extension requires a maintainer. In the event an - extension author would like to move beyond the project, the project should - 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. -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 - are installed automatically. If tests are invoked with ``python setup.py - test``, test dependencies can be specified in the :file:`setup.py` file. The - test suite also has to be part of the distribution. -3. APIs of approved extensions will be checked for the following - characteristics: +0. An approved Flask extension requires a maintainer. In the event an + extension author would like to move beyond the project, the project should + 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. +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 + are installed automatically. If tests are invoked with ``python setup.py + test``, test dependencies can be specified in the `setup.py` file. The + test suite also has to be part of the distribution. +3. APIs of approved extensions will be checked for the following + characteristics: - - an approved extension has to support multiple applications - running in the same Python process. - - it must be possible to use the factory pattern for creating - applications. + - an approved extension has to support multiple applications + running in the same Python process. + - it must be possible to use the factory pattern for creating + applications. -4. The license must be BSD/MIT/WTFPL licensed. -5. The naming scheme for official extensions is *Flask-ExtensionName* or - *ExtensionName-Flask*. -6. Approved extensions must define all their dependencies in the - :file:`setup.py` file unless a dependency cannot be met because it is not - available on PyPI. -7. The extension must have documentation that uses one of the two Flask - themes for Sphinx documentation. -8. The setup.py description (and thus the PyPI description) has to - link to the documentation, website (if there is one) and there - must be a link to automatically install the development version - (``PackageName==dev``). -9. The ``zip_safe`` flag in the setup script must be set to ``False``, - even if the extension would be safe for zipping. -10. An extension currently has to support Python 2.6 as well as - Python 2.7 +4. The license must be BSD/MIT/WTFPL licensed. +5. The naming scheme for official extensions is *Flask-ExtensionName* or + *ExtensionName-Flask*. +6. Approved extensions must define all their dependencies in the + `setup.py` file unless a dependency cannot be met because it is not + available on PyPI. +7. The extension must have documentation that uses one of the two Flask + themes for Sphinx documentation. +8. The ``zip_safe`` flag in the setup script must be set to ``False``, + even if the extension would be safe for zipping. +9. An extension currently has to support Python 2.6 as well as + Python 2.7 .. _ext-import-transition: From 3185f445c908a3926f17deb574cb9b1f0b049fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 26 Apr 2014 02:04:21 +0200 Subject: [PATCH 0006/1888] Drop Python 2.6 minimum requirement for extensions Python 2.6 is not supported by python-dev anymore and does not get any security updates. Even though Flask supports 2.6 at the moment, I think it's not necessary for any extensions that are going to be approved in the future to support 2.6. --- docs/extensiondev.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 395227ec..8b18e726 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -386,8 +386,7 @@ extension to be approved you have to follow these guidelines: themes for Sphinx documentation. 8. The ``zip_safe`` flag in the setup script must be set to ``False``, even if the extension would be safe for zipping. -9. An extension currently has to support Python 2.6 as well as - Python 2.7 +9. An extension currently has to support Python 2.7. .. _ext-import-transition: From b12d9762e7fdb4877ea0cb750628149609e3ecb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 26 Apr 2014 02:06:09 +0200 Subject: [PATCH 0007/1888] Require Python 3.3 and higher for extensions Flask and several extensions already supports Python 3.3 and higher. By requiring approved extensions to support Python 3.3 as well we can quickly achieve better Python 3 adoption and make using Python 3 easier for users. The effort of supporting both Python 2.7 and Python 3.3 is small enough that it shouldn't be a problem to require this from extension authors. --- docs/extensiondev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 8b18e726..5b20528e 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -386,7 +386,7 @@ extension to be approved you have to follow these guidelines: themes for Sphinx documentation. 8. The ``zip_safe`` flag in the setup script must be set to ``False``, even if the extension would be safe for zipping. -9. An extension currently has to support Python 2.7. +9. An extension currently has to support Python 2.7, Python 3.3 and higher. .. _ext-import-transition: From b9938d0182abac79289963b2df507756bf86a50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sun, 27 Apr 2014 00:26:39 +0200 Subject: [PATCH 0008/1888] Don't allow namespace packages anymore --- docs/extensiondev.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 5b20528e..000892e6 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 From 2b58e6120ceadf363d12cc12889bfc79c18d5066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sun, 27 Apr 2014 00:27:37 +0200 Subject: [PATCH 0009/1888] Fix wording --- docs/extensiondev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 000892e6..e8028ffc 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -375,7 +375,7 @@ extension to be approved you have to follow these guidelines: - it must be possible to use the factory pattern for creating applications. -4. The license must be BSD/MIT/WTFPL licensed. +4. The extension must be BSD/MIT/WTFPL licensed. 5. The naming scheme for official extensions is *Flask-ExtensionName* or *ExtensionName-Flask*. 6. Approved extensions must define all their dependencies in the From f80ea4fe5d3f194c567d3fd279e1e84050a12b10 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Sat, 25 Oct 2014 18:56:10 -0700 Subject: [PATCH 0010/1888] Some grammar and typo fixes --- docs/extensiondev.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index e8028ffc..2f8aae39 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -48,7 +48,7 @@ that people can easily install the development version into their virtualenv without having to download the library by hand. Flask extensions must be licensed under a BSD, MIT or more liberal license -to be able to be enlisted in the Flask Extension Registry. Keep in mind +in order to be listed in the Flask Extension Registry. Keep in mind that the Flask Extension Registry is a moderated place and libraries will be reviewed upfront if they behave as required. @@ -154,10 +154,10 @@ What to use depends on what you have in mind. For the SQLite 3 extension we will use the class-based approach because it will provide users with an object that handles opening and closing database connections. -What's important about classes is that they encourage to be shared around -on module level. In that case, the object itself must not under any +When designing your classes, it's important to make them easily reusable +at the module level. This means the object itself must not under any circumstances store any application specific state and must be shareable -between different application. +between different applications. The Extension Code ------------------ @@ -334,10 +334,10 @@ development. If you want to learn more, it's a very good idea to check out existing extensions on the `Flask Extension Registry`_. If you feel lost there is still the `mailinglist`_ and the `IRC channel`_ to get some ideas for nice looking APIs. Especially if you do something nobody before -you did, it might be a very good idea to get some more input. This not -only to get an idea about what people might want to have from an -extension, but also to avoid having multiple developers working on pretty -much the same side by side. +you did, it might be a very good idea to get some more input. This not only +generates useful feedback on what people might want from an extension, but +also avoids having multiple developers working in isolation on pretty much the +same problem. Remember: good API design is hard, so introduce your project on the mailinglist, and let other developers give you a helping hand with From 011b129b6bc615c7a24de626dd63b6a311fa6ce6 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 8 Jun 2015 16:31:35 -0500 Subject: [PATCH 0011/1888] 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 0012/1888] 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 5505827a4bd759a24696258983ec14c768358af8 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 14 Sep 2015 13:25:52 -0500 Subject: [PATCH 0013/1888] 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 0014/1888] 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 51c35295af94f8f401a4942aab674f161222a984 Mon Sep 17 00:00:00 2001 From: Nadav Geva Date: Thu, 15 Oct 2015 00:02:46 +0300 Subject: [PATCH 0015/1888] Fix name mismatch in apierrors page --- 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 264b9ae5..b06966e6 100644 --- a/docs/patterns/apierrors.rst +++ b/docs/patterns/apierrors.rst @@ -47,7 +47,7 @@ At that point views can raise that error, but it would immediately result in an internal server error. The reason for this is that there is no handler registered for this error class. That however is easy to add:: - @app.errorhandler(InvalidAPIUsage) + @app.errorhandler(InvalidUsage) def handle_invalid_usage(error): response = jsonify(error.to_dict()) response.status_code = error.status_code From daa3f272da477470d23481d409d54f989136ac32 Mon Sep 17 00:00:00 2001 From: Ivan Velichko Date: Fri, 20 Nov 2015 19:43:49 +0300 Subject: [PATCH 0016/1888] Allow to specify subdomain and/or url_scheme in app.test_request_context() --- flask/app.py | 6 +++++- flask/testing.py | 19 +++++++++++++++---- flask/testsuite/testing.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/flask/app.py b/flask/app.py index e073798a..8a191d0a 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1776,7 +1776,11 @@ class Flask(_PackageBoundObject): def test_request_context(self, *args, **kwargs): """Creates a WSGI environment from the given values (see :func:`werkzeug.test.EnvironBuilder` for more information, this - function accepts the same arguments). + function accepts the same arguments plus two additional). + + Additional arguments (might be used only if `base_url` is not specified): + :param subdomain: subdomain in case of testing requests handled by blueprint + :param url_scheme: a URL scheme (default scheme is http) """ from flask.testing import make_test_environ_builder builder = make_test_environ_builder(self, *args, **kwargs) diff --git a/flask/testing.py b/flask/testing.py index 6351462b..73043f1b 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -20,13 +20,24 @@ except ImportError: from urlparse import urlsplit as url_parse -def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): +def make_test_environ_builder(app, path='/', base_url=None, subdomain=None, + url_scheme=None, *args, **kwargs): """Creates a new test builder with some application defaults thrown in.""" - http_host = app.config.get('SERVER_NAME') - app_root = app.config.get('APPLICATION_ROOT') + assert not (base_url or subdomain or url_scheme) \ + or (base_url is not None) != bool(subdomain or url_scheme), \ + 'If "base_url" parameter is passed in, pass of ' \ + '"subdomain" and/or "url_scheme" is meaningless.' + if base_url is None: + http_host = app.config.get('SERVER_NAME') + app_root = app.config.get('APPLICATION_ROOT') + if subdomain: + http_host = '%s.%s' % (subdomain, http_host) + if url_scheme is None: + url_scheme = 'http' + url = url_parse(path) - base_url = 'http://%s/' % (url.netloc or http_host or 'localhost') + base_url = '%s://%s/' % (url_scheme, url.netloc or http_host or 'localhost') if app_root: base_url += app_root.lstrip('/') if url.netloc: diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 11ab763b..9d58fef4 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -45,6 +45,38 @@ class TestToolsTestCase(FlaskTestCase): rv = c.get('/') self.assert_equal(rv.data, b'http://localhost/') + def test_specify_url_scheme(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context(url_scheme='https') + self.assert_equal(ctx.request.url, 'https://localhost/') + with app.test_client() as c: + rv = c.get('/', url_scheme='https') + self.assert_equal(rv.data, b'https://localhost/') + + def test_blueprint_with_subdomain(self): + app = flask.Flask(__name__) + app.testing = True + app.config['SERVER_NAME'] = 'example.com:1234' + app.config['APPLICATION_ROOT'] = '/foo' + + bp = flask.Blueprint('company', __name__, subdomain='xxx') + @bp.route('/') + def index(): + return flask.request.url + app.register_blueprint(bp) + + ctx = app.test_request_context('/', subdomain='xxx') + self.assert_equal(ctx.request.url, 'http://xxx.example.com:1234/foo/') + self.assert_equal(ctx.request.blueprint, bp.name) + with app.test_client() as c: + rv = c.get('/', subdomain='xxx') + self.assert_equal(rv.data, b'http://xxx.example.com:1234/foo/') + def test_redirect_keep_session(self): app = flask.Flask(__name__) app.secret_key = 'testing' From 826d7475cdab43b85d79f99e8eae5dabb01baf7b Mon Sep 17 00:00:00 2001 From: Aviv Cohn Date: Tue, 5 Jan 2016 01:10:35 +0200 Subject: [PATCH 0017/1888] 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 b23cd61e04b01e83defcdb9dafe8b6bcafdffc40 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 May 2016 11:02:48 +0200 Subject: [PATCH 0018/1888] 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 0019/1888] 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 0020/1888] 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 0021/1888] 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 0022/1888] 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 0023/1888] 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 0024/1888] 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 0025/1888] 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 0026/1888] 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 0027/1888] 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 0028/1888] 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 0029/1888] 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 0030/1888] 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 0031/1888] 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 0032/1888] 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 0033/1888] 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 0034/1888] 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 0035/1888] 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 0036/1888] 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 0037/1888] 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 0038/1888] 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 0039/1888] 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 0040/1888] 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 0041/1888] 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 0042/1888] 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 0043/1888] 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 | 154 ++++++++++++++++++++----------------- 5 files changed, 110 insertions(+), 92 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): + 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 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 = 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 0044/1888] 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 0045/1888] 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 0046/1888] 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 0047/1888] 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 0048/1888] 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 0049/1888] 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 0050/1888] 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 0051/1888] 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 0052/1888] 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 0053/1888] 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 19dcdf4e70e3c57ef647c16c63f6633241c0c946 Mon Sep 17 00:00:00 2001 From: Zev Averbach Date: Fri, 3 Jun 2016 16:59:15 -0700 Subject: [PATCH 0054/1888] enumerates the states in which code is executed... ... and fixes an awkward phrasing. --- docs/appcontext.rst | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index baa71315..44a21785 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -5,12 +5,15 @@ The Application Context .. versionadded:: 0.9 -One of the design ideas behind Flask is that there are two different -“states” in which code is executed. The application setup state in which -the application implicitly is on the module level. It starts when the -:class:`Flask` object is instantiated, and it implicitly ends when the -first request comes in. While the application is in this state a few -assumptions are true: +One of the design ideas behind Flask is that there are at least two +different “states” in which code is executed: + +1. The application setup state, in which the application implicitly is +on the module level. + +This state starts when the :class:`Flask` object is instantiated, and +it implicitly ends when the first request comes in. While the +application is in this state, a few assumptions are true: - the programmer can modify the application object safely. - no request handling happened so far @@ -18,18 +21,21 @@ assumptions are true: modify it, there is no magic proxy that can give you a reference to the application object you're currently creating or modifying. -In contrast, during request handling, a couple of other rules exist: +2. In contrast, in the request handling state, a couple of other rules +exist: - while a request is active, the context local objects (:data:`flask.request` and others) point to the current request. - any code can get hold of these objects at any time. -There is a third state which is sitting in between a little bit. +3. There is also a third state somewhere in between 'module-level' and +'request-handling': + Sometimes you are dealing with an application in a way that is similar to -how you interact with applications during request handling; 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. +how you interact with applications during request handling, but without +there being an active request. Consider, for instance, that you're +sitting in an interactive Python shell and interacting with the +application, or a command line application. The application context is what powers the :data:`~flask.current_app` context local. From 9c236d3b84332aef0c834266a0e3d2171fe5a7bd Mon Sep 17 00:00:00 2001 From: RamiC Date: Sat, 4 Jun 2016 09:25:16 +0300 Subject: [PATCH 0055/1888] 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 0056/1888] 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 0057/1888] 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 0058/1888] 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 0059/1888] 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 0060/1888] 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 0061/1888] 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 0062/1888] 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 0063/1888] use pip instead of setup.py in fabric command --- docs/patterns/fabric.rst | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 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,37 +43,26 @@ 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 + filename = '%s.tar.gz' % dist + + # upload the package to the temporary folder on the server + put('dist/%s' % filename, '/tmp/%s' % filename) + + # install the package in the application's virtualenv with pip + run('/var/www/yourapplication/env/bin/pip install /tmp/%s' % filename) + + # 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') -The example above is well documented and should be straightforward. Here -a recap of the most common commands fabric provides: - -- `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. - 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 0064/1888] 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 0065/1888] 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 0066/1888] 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 0067/1888] 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 0068/1888] 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 0069/1888] 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 0070/1888] 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 0071/1888] 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 0072/1888] 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 0073/1888] 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 0074/1888] 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 0075/1888] 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 0076/1888] 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 0077/1888] 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 0514ba2de1751de1b0c9a223c96f6a7145714e6a Mon Sep 17 00:00:00 2001 From: Dave Barker Date: Tue, 14 Jun 2016 00:59:32 +0100 Subject: [PATCH 0078/1888] Enable template auto-reloading in app.run() When Flask app debugging is enabled (app.debug==True), and Jinja2 template auto-reloading is not explicitly disbaled, template auto-reloading should be enabled. If the app is instantiated, the jinja_env object is accessed (thereby initialising the Jinja2 environment) and the server is then started with app.run(debug=True), template auto-reloading is *not* enabled. This is because reading the jinja_env object causes the environment initialisation function to set auto_reload to app.debug (which isn't yet True). Calling app.run(debug=True) should correct this in order to remain consistent with Flask code reloading (which is enabled within app.run() if debug == True). --- flask/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/app.py b/flask/app.py index b1ea0464..1d32ece9 100644 --- a/flask/app.py +++ b/flask/app.py @@ -839,6 +839,8 @@ class Flask(_PackageBoundObject): options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) options.setdefault('passthrough_errors', True) + if debug: + self.jinja_env.auto_reload = True try: run_simple(host, port, self, **options) finally: From 5c12721730d18e2eddfe083cf1f6398af8915643 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 14 Jun 2016 22:45:24 +0200 Subject: [PATCH 0079/1888] 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 24289e97af233f11d9c145f80604b2ee416254a1 Mon Sep 17 00:00:00 2001 From: Dave Barker Date: Wed, 15 Jun 2016 02:15:33 +0100 Subject: [PATCH 0080/1888] Add test for new template auto reload debug behaviour --- tests/test_templating.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_templating.py b/tests/test_templating.py index b60a592a..e65fe834 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -14,6 +14,7 @@ import pytest import flask import logging from jinja2 import TemplateNotFound +import werkzeug.serving def test_context_processing(): @@ -346,6 +347,22 @@ def test_templates_auto_reload(): app.config['TEMPLATES_AUTO_RELOAD'] = True assert app.jinja_env.auto_reload is True +def test_templates_auto_reload_debug_run(monkeypatch): + # debug is None in config, config option is None, app.run(debug=True) + 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) + + assert app.config['TEMPLATES_AUTO_RELOAD'] is None + assert app.jinja_env.auto_reload is False + app.run(debug=True) + assert app.jinja_env.auto_reload is True + def test_template_loader_debugging(test_apps): from blueprintapp import app From 1a67e284d043818743ae9ef5a46b6b64a9db56f7 Mon Sep 17 00:00:00 2001 From: Dave Barker Date: Wed, 15 Jun 2016 02:25:48 +0100 Subject: [PATCH 0081/1888] Remove unnecessary werkzeug mock attribs from test --- tests/test_templating.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_templating.py b/tests/test_templating.py index e65fe834..aa3d7409 100644 --- a/tests/test_templating.py +++ b/tests/test_templating.py @@ -349,11 +349,9 @@ def test_templates_auto_reload(): def test_templates_auto_reload_debug_run(monkeypatch): # debug is None in config, config option is None, app.run(debug=True) - rv = {} - # Mocks werkzeug.serving.run_simple method def run_simple_mock(*args, **kwargs): - rv['passthrough_errors'] = kwargs.get('passthrough_errors') + pass app = flask.Flask(__name__) monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) From c0087204e5b83ad4e3983d07b66d17acdde0b9de Mon Sep 17 00:00:00 2001 From: Leo Tindall Date: Tue, 14 Jun 2016 23:55:47 -0700 Subject: [PATCH 0082/1888] 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 0083/1888] 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 0084/1888] 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 0085/1888] 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 %}