From 3765cc2e9e6ea2d227f898d5b176aa50680e491c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 29 Sep 2011 23:36:57 +0200 Subject: [PATCH 0001/2940] This is 0.9-dev --- CHANGES | 5 +++++ flask/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e1a3733a..d59a8b66 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.9 +----------- + +Relase date to be decided, codename to be chosen. + Version 0.8 ----------- diff --git a/flask/__init__.py b/flask/__init__.py index 73019aa8..54bfedda 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.8' +__version__ = '0.9-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. diff --git a/setup.py b/setup.py index 4e221bd9..d41a3eca 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ class run_audit(Command): setup( name='Flask', - version='0.8', + version='0.9-dev', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 1759d8e4d80df3c3dc709c9bace2eb07d5685596 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 6 Oct 2011 10:57:03 -0400 Subject: [PATCH 0002/2940] Added support for anchor link generation. --- CHANGES | 3 +++ flask/helpers.py | 11 ++++++++++- flask/testsuite/helpers.py | 9 +++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a1b9fedc..76ac8a22 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Version 0.9 Relase date to be decided, codename to be chosen. +- The :func:`flask.url_for` function now can generate anchors to the + generated links. + Version 0.8.1 ------------- diff --git a/flask/helpers.py b/flask/helpers.py index 72c8f170..2471103d 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -18,6 +18,7 @@ import mimetypes from time import time from zlib import adler32 from threading import RLock +from werkzeug.urls import url_quote # try to load the best simplejson implementation available. If JSON # is not installed, we add a failing class. @@ -184,9 +185,13 @@ def url_for(endpoint, **values): For more information, head over to the :ref:`Quickstart `. + .. versionadded:: 0.9 + The `_anchor` parameter was added. + :param endpoint: the endpoint of the URL (name of the function) :param values: the variable arguments of the URL rule :param _external: if set to `True`, an absolute URL is generated. + :param _anchor: if provided this is added as anchor to the URL. """ ctx = _request_ctx_stack.top blueprint_name = request.blueprint @@ -204,8 +209,12 @@ def url_for(endpoint, **values): elif endpoint.startswith('.'): endpoint = endpoint[1:] external = values.pop('_external', False) + anchor = values.pop('_anchor', None) ctx.app.inject_url_defaults(endpoint, values) - return ctx.url_adapter.build(endpoint, values, force_external=external) + rv = ctx.url_adapter.build(endpoint, values, force_external=external) + if anchor is not None: + rv += '#' + url_quote(anchor) + return rv def get_template_attribute(template_name, attribute): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 052d36e1..80729821 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -288,6 +288,15 @@ class LoggingTestCase(FlaskTestCase): self.assert_equal(rv.status_code, 500) self.assert_equal(rv.data, 'Hello Server Error') + def test_url_for_with_anchor(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return '42' + with app.test_request_context(): + self.assert_equal(flask.url_for('index', _anchor='x y'), + '/#x%20y') + def suite(): suite = unittest.TestSuite() From b115b38dbec3598735c57e1f48610cd82b161af1 Mon Sep 17 00:00:00 2001 From: kracekumar Date: Thu, 6 Oct 2011 23:45:35 +0530 Subject: [PATCH 0003/2940] Added PasswordField in docs/patterns/wtforms.rst --- docs/patterns/wtforms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 93824df7..ed530427 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -26,7 +26,7 @@ The Forms This is an example form for a typical registration page:: - from wtforms import Form, BooleanField, TextField, validators + from wtforms import Form, BooleanField, TextField, PasswordField, validators class RegistrationForm(Form): username = TextField('Username', [validators.Length(min=4, max=25)]) From ebd74688070617a4cb6dd39df616f4691a98666d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 7 Oct 2011 15:09:02 -0400 Subject: [PATCH 0004/2940] Added a newline --- flask/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flask/helpers.py b/flask/helpers.py index 2471103d..62b6a3a4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -56,6 +56,7 @@ def _assert_have_json(): if not json_available: raise RuntimeError('simplejson not installed') + # figure out if simplejson escapes slashes. This behaviour was changed # from one version to another without reason. if not json_available or '\\/' not in json.dumps('/'): From 230e136f32746c5dbd4e06dbad3e329807440c71 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 7 Oct 2011 15:09:12 -0400 Subject: [PATCH 0005/2940] Subscribe to signals with extra kwargs in the docs --- docs/signals.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/signals.rst b/docs/signals.rst index 0d1d9eea..c381da92 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -86,7 +86,7 @@ specified that way one has to pass the list in as argument:: from flask import template_rendered - def captured_templates(app, recorded): + def captured_templates(app, recorded, **extra): def record(sender, template, context): recorded.append((template, context)) return template_rendered.connected_to(record, app) @@ -94,7 +94,7 @@ specified that way one has to pass the list in as argument:: The example above would then look like this:: templates = [] - with captured_templates(app, templates): + with captured_templates(app, templates, **extra): ... template, context = templates[0] @@ -162,7 +162,7 @@ With Blinker 1.1 you can also easily subscribe to signals by using the new from flask import template_rendered @template_rendered.connect_via(app) - def when_template_rendered(sender, template, context): + def when_template_rendered(sender, template, context, **extra): print 'Template %s is rendered with %s' % (template.name, context) Core Signals @@ -181,7 +181,7 @@ The following signals exist in Flask: Example subscriber:: - def log_template_renders(sender, template, context): + def log_template_renders(sender, template, context, **extra): sender.logger.debug('Rendering template "%s" with context %s', template.name or 'string template', context) @@ -199,7 +199,7 @@ The following signals exist in Flask: Example subscriber:: - def log_request(sender): + def log_request(sender, **extra): sender.logger.debug('Request context is set up') from flask import request_started @@ -213,7 +213,7 @@ The following signals exist in Flask: Example subscriber:: - def log_response(sender, response): + def log_response(sender, response, **extra): sender.logger.debug('Request context is about to close down. ' 'Response: %s', response) @@ -230,7 +230,7 @@ The following signals exist in Flask: Example subscriber:: - def log_exception(sender, exception): + def log_exception(sender, exception, **extra): sender.logger.debug('Got exception during processing: %s', exception) from flask import got_request_exception @@ -246,7 +246,7 @@ The following signals exist in Flask: Example subscriber:: - def close_db_connection(sender): + def close_db_connection(sender, **extra): session.close() from flask import request_tearing_down From 174f32250ec54950badbaf3878d9d6acc8328ddd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 7 Oct 2011 15:19:03 -0400 Subject: [PATCH 0006/2940] Mention that people subscribe with **extra. --- docs/signals.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/signals.rst b/docs/signals.rst index c381da92..75487800 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -55,7 +55,7 @@ to the template:: @contextmanager def captured_templates(app): recorded = [] - def record(sender, template, context): + def record(sender, template, context, **extra): recorded.append((template, context)) template_rendered.connect(record, app) try: @@ -73,6 +73,9 @@ This can now easily be paired with a test client:: assert template.name == 'index.html' assert len(context['items']) == 10 +Make sure to subscribe with an extra ``**extra`` argument so that your +calls don't fail if Flask introduces new arguments to the signals. + All the template rendering in the code issued by the application `app` in the body of the `with` block will now be recorded in the `templates` variable. Whenever a template is rendered, the template object as well as From 61a95196ac28771466ada43a1c01effda42040a5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 11 Oct 2011 19:09:37 -0700 Subject: [PATCH 0007/2940] Changed logic for debug level log settings --- CHANGES | 2 ++ flask/logging.py | 4 +++- flask/testsuite/helpers.py | 7 +++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 76ac8a22..aa7fcf34 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ Relase date to be decided, codename to be chosen. - The :func:`flask.url_for` function now can generate anchors to the generated links. +- Logger now only returns the debug log setting if it was not set + explicitly. Version 0.8.1 ------------- diff --git a/flask/logging.py b/flask/logging.py index b992aef8..9ad641d1 100644 --- a/flask/logging.py +++ b/flask/logging.py @@ -25,7 +25,9 @@ def create_logger(app): class DebugLogger(Logger): def getEffectiveLevel(x): - return DEBUG if app.debug else Logger.getEffectiveLevel(x) + if x.level == 0 and app.debug: + return DEBUG + return Logger.getEffectiveLevel(x) class DebugHandler(StreamHandler): def emit(x, record): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 80729821..06b0542d 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -246,6 +246,13 @@ class LoggingTestCase(FlaskTestCase): else: self.assert_(False, 'debug log ate the exception') + def test_debug_log_override(self): + app = flask.Flask(__name__) + app.debug = True + app.logger_name = 'flask_tests/test_debug_log_override' + app.logger.level = 10 + self.assert_equal(app.logger.level, 10) + def test_exception_logging(self): out = StringIO() app = flask.Flask(__name__) From 4ab97047dee359af1afce6fe5431ffa7c5ed5795 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 13 Oct 2011 11:49:51 +0300 Subject: [PATCH 0008/2940] Typo fix --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index aa7fcf34..6dd66b09 100644 --- a/CHANGES +++ b/CHANGES @@ -46,7 +46,7 @@ Released on September 29th 2011, codename Rakija of a value error which usually would result in a 500 internal server error if not handled. This is a backwards incompatible change. - Applications now not only have a root path where the resources and modules - are located but also an instane path which is the designated place to + are located but also an instance path which is the designated place to drop files that are modified at runtime (uploads etc.). Also this is conceptionally only instance depending and outside version control so it's the perfect place to put configuration files etc. For more information From 11c9bf29437ce847089703876cdec8cea68ca594 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Mon, 17 Oct 2011 13:12:12 +0000 Subject: [PATCH 0009/2940] Api fix status_code => status --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 7695788e..47ff84e5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -144,7 +144,7 @@ Response Objects A :class:`Headers` object representing the response headers. - .. attribute:: status_code + .. attribute:: status The response status as integer. From e4d9ccd6ec889a8b52b28953c0bb6d490db7df63 Mon Sep 17 00:00:00 2001 From: Cory Li Date: Wed, 2 Nov 2011 01:45:39 -0300 Subject: [PATCH 0010/2940] Changing instance_root to instance_path --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index ca724dce..2f9d8307 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -375,7 +375,7 @@ file from the instance folder with :meth:`Flask.open_instance_resource`. Example usage for both:: - filename = os.path.join(app.instance_root, 'application.cfg') + filename = os.path.join(app.instance_path, 'application.cfg') with open(filename) as f: config = f.read() From f52e7a9dc944f425c2f2a77706bc2af98b23295c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 4 Nov 2011 02:46:22 +0100 Subject: [PATCH 0011/2940] Added support for _method to url_for() --- CHANGES | 2 ++ flask/helpers.py | 7 +++++-- flask/testsuite/helpers.py | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index aa7fcf34..3562bf90 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,8 @@ Relase date to be decided, codename to be chosen. - The :func:`flask.url_for` function now can generate anchors to the generated links. +- The :func:`flask.url_for` function now can also explicitly generate + URL rules specific to a given HTTP method. - Logger now only returns the debug log setting if it was not set explicitly. diff --git a/flask/helpers.py b/flask/helpers.py index 62b6a3a4..7295dc3c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -187,12 +187,13 @@ def url_for(endpoint, **values): For more information, head over to the :ref:`Quickstart `. .. versionadded:: 0.9 - The `_anchor` parameter was added. + The `_anchor` and `_method` parameters were added. :param endpoint: the endpoint of the URL (name of the function) :param values: the variable arguments of the URL rule :param _external: if set to `True`, an absolute URL is generated. :param _anchor: if provided this is added as anchor to the URL. + :param _method: if provided this explicitly specifies an HTTP method. """ ctx = _request_ctx_stack.top blueprint_name = request.blueprint @@ -211,8 +212,10 @@ def url_for(endpoint, **values): endpoint = endpoint[1:] external = values.pop('_external', False) anchor = values.pop('_anchor', None) + method = values.pop('_method', None) ctx.app.inject_url_defaults(endpoint, values) - rv = ctx.url_adapter.build(endpoint, values, force_external=external) + rv = ctx.url_adapter.build(endpoint, values, method=method, + force_external=external) if anchor is not None: rv += '#' + url_quote(anchor) return rv diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 06b0542d..41e31be9 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -304,6 +304,32 @@ class LoggingTestCase(FlaskTestCase): self.assert_equal(flask.url_for('index', _anchor='x y'), '/#x%20y') + def test_url_with_method(self): + from flask.views import MethodView + app = flask.Flask(__name__) + class MyView(MethodView): + def get(self, id=None): + if id is None: + return 'List' + return 'Get %d' % id + def post(self): + return 'Create' + myview = MyView.as_view('myview') + app.add_url_rule('/myview/', methods=['GET'], + view_func=myview) + app.add_url_rule('/myview/', methods=['GET'], + view_func=myview) + app.add_url_rule('/myview/create', methods=['POST'], + view_func=myview) + + with app.test_request_context(): + self.assert_equal(flask.url_for('myview', _method='GET'), + '/myview/') + self.assert_equal(flask.url_for('myview', id=42, _method='GET'), + '/myview/42') + self.assert_equal(flask.url_for('myview', _method='POST'), + '/myview/create') + def suite(): suite = unittest.TestSuite() From 51e4a58a85bc1500d1400ba7d633d570f6296745 Mon Sep 17 00:00:00 2001 From: Jim Rollenhagen Date: Sat, 5 Nov 2011 09:02:05 -0400 Subject: [PATCH 0012/2940] Fix flask issue #338 Give POST its own url_rule to avoid TypeError. --- docs/views.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/views.rst b/docs/views.rst index 441620a6..37904729 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -210,7 +210,8 @@ and explicitly mentioning the methods for each:: user_view = UserAPI.as_view('user_api') app.add_url_rule('/users/', defaults={'user_id': None}, - view_func=user_view, methods=['GET', 'POST']) + view_func=user_view, methods=['GET',]) + app.add_url_rule('/users/', view_func=user_view, methods=['POST',]) app.add_url_rule('/users/', view_func=user_view, methods=['GET', 'PUT', 'DELETE']) @@ -220,7 +221,8 @@ registration code:: def register_api(view, endpoint, url, pk='id', pk_type='int'): view_func = view.as_view(endpoint) app.add_url_rule(url, defaults={pk: None}, - view_func=view_func, methods=['GET', 'POST']) + view_func=view_func, methods=['GET',]) + app.add_url_rule(url, view_func=view_func, methods=['POST',]) app.add_url_rule('%s<%s:%s>' % (url, pk), view_func=view_func, methods=['GET', 'PUT', 'DELETE']) From e345a3afb5bf6ebf20e566f0846a58d6f55971eb Mon Sep 17 00:00:00 2001 From: Jim Rollenhagen Date: Sat, 5 Nov 2011 09:03:18 -0400 Subject: [PATCH 0013/2940] Fix string format error while we're in here. --- docs/views.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/views.rst b/docs/views.rst index 37904729..a48d81f2 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -223,7 +223,7 @@ registration code:: app.add_url_rule(url, defaults={pk: None}, view_func=view_func, methods=['GET',]) app.add_url_rule(url, view_func=view_func, methods=['POST',]) - app.add_url_rule('%s<%s:%s>' % (url, pk), view_func=view_func, + app.add_url_rule('%s<%s:%s>' % (url, pk, pk_type), view_func=view_func, methods=['GET', 'PUT', 'DELETE']) register_api(UserAPI, 'user_api', '/users/', pk='user_id') From 7f4c12b33565c4d0802c4385bb2bbaa6c5735196 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 5 Nov 2011 17:43:40 +0100 Subject: [PATCH 0014/2940] Break up a circular dependency on shutdown --- CHANGES | 6 +++ flask/ctx.py | 4 ++ flask/testsuite/regression.py | 83 +++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 flask/testsuite/regression.py diff --git a/CHANGES b/CHANGES index ff6bdef2..e30979d8 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,12 @@ Relase date to be decided, codename to be chosen. URL rules specific to a given HTTP method. - Logger now only returns the debug log setting if it was not set explicitly. +- Unregister a circular dependency between the WSGI environment and + the request object when shutting down the request. This means that + environ ``werkzeug.request`` will be `None` after the response was + returned to the WSGI server but has the advantage that the garbage + collector is not needed on CPython to tear down the request unless + the user created circular dependencies themselves. Version 0.8.1 ------------- diff --git a/flask/ctx.py b/flask/ctx.py index 26781dbd..9a72d251 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -150,6 +150,10 @@ class RequestContext(object): assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ % (rv, self) + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + rv.request.environ['werkzeug.request'] = None + def __enter__(self): self.push() return self diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py new file mode 100644 index 00000000..51a866a4 --- /dev/null +++ b/flask/testsuite/regression.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.regression + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests regressions. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import gc +import sys +import flask +import threading +import unittest +from werkzeug.test import run_wsgi_app, create_environ +from flask.testsuite import FlaskTestCase + + +_gc_lock = threading.Lock() + + +class _NoLeakAsserter(object): + + def __init__(self, testcase): + self.testcase = testcase + + def __enter__(self): + gc.disable() + _gc_lock.acquire() + loc = flask._request_ctx_stack._local + + # Force Python to track this dictionary at all times. + # This is necessary since Python only starts tracking + # dicts if they contain mutable objects. It's a horrible, + # horrible hack but makes this kinda testable. + loc.__storage__['FOOO'] = [1, 2, 3] + + gc.collect() + self.old_objects = len(gc.get_objects()) + + def __exit__(self, exc_type, exc_value, tb): + if not hasattr(sys, 'getrefcount'): + gc.collect() + new_objects = len(gc.get_objects()) + if new_objects > self.old_objects: + self.testcase.fail('Example code leaked') + _gc_lock.release() + gc.enable() + + +class MemoryTestCase(FlaskTestCase): + + def assert_no_leak(self): + return _NoLeakAsserter(self) + + def test_memory_consumption(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template('simple_template.html', whiskey=42) + + def fire(): + with app.test_client() as c: + rv = c.get('/') + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, '

42

') + + # Trigger caches + fire() + + with self.assert_no_leak(): + for x in xrange(10): + fire() + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(MemoryTestCase)) + return suite From 9dd61eea6b49bfe1cc4639f5bc4c1371df938d95 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Tue, 8 Nov 2011 10:31:45 +0200 Subject: [PATCH 0015/2940] Added testcase for redirect and session keeparound bug --- flask/testsuite/testing.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 6574e77d..f9ee909b 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -46,6 +46,32 @@ class TestToolsTestCase(FlaskTestCase): rv = c.get('/') self.assert_equal(rv.data, 'http://localhost/') + def test_redirect_keep_session(self): + app = flask.Flask(__name__) + app.secret_key = 'testing' + + @app.route('/', methods=['GET', 'POST']) + def index(): + if flask.request.method == 'POST': + return flask.redirect('/redirect') + flask.session['data'] = 'foo' + return 'index' + + @app.route('/redirect') + def redirect(): + return 'redirect' + + with app.test_client() as c: + ctx = app.test_request_context() + ctx.push() + rv = c.get('/') + assert rv.data == 'index' + assert flask.session.get('data') == 'foo' + rv = c.post('/', data={}, follow_redirects=True) + assert rv.data == 'redirect' + assert flask.session.get('data') == 'foo' + ctx.pop() + def test_session_transactions(self): app = flask.Flask(__name__) app.testing = True From d628df6ab6c57b34acfb412e135d0d095636c539 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 20 Nov 2011 16:54:40 +0100 Subject: [PATCH 0016/2940] Store session after callbacks. This fixes #351 --- CHANGES | 3 +++ flask/app.py | 4 ++-- flask/testsuite/basic.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e30979d8..61723dca 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,9 @@ Relase date to be decided, codename to be chosen. returned to the WSGI server but has the advantage that the garbage collector is not needed on CPython to tear down the request unless the user created circular dependencies themselves. +- Session is now stored after callbacks so that if the session payload + is stored in the session you can still modify it in an after + request callback. Version 0.8.1 ------------- diff --git a/flask/app.py b/flask/app.py index ebf4e6a6..42ffea4d 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1403,8 +1403,6 @@ class Flask(_PackageBoundObject): """ ctx = _request_ctx_stack.top bp = ctx.request.blueprint - if not self.session_interface.is_null_session(ctx.session): - self.save_session(ctx.session, response) funcs = () if bp is not None and bp in self.after_request_funcs: funcs = reversed(self.after_request_funcs[bp]) @@ -1412,6 +1410,8 @@ class Flask(_PackageBoundObject): funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: response = handler(response) + if not self.session_interface.is_null_session(ctx.session): + self.save_session(ctx.session, response) return response def do_teardown_request(self): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 1733f0a3..a11f7806 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -279,6 +279,23 @@ class BasicFunctionalityTestCase(FlaskTestCase): match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) self.assert_(match is None) + def test_session_stored_last(self): + app = flask.Flask(__name__) + app.secret_key = 'development-key' + app.testing = True + + @app.after_request + def modify_session(response): + flask.session['foo'] = 42 + return response + @app.route('/') + def dump_session_contents(): + return repr(flask.session.get('foo')) + + c = app.test_client() + self.assert_equal(c.get('/').data, 'None') + self.assert_equal(c.get('/').data, '42') + def test_flashes(self): app = flask.Flask(__name__) app.secret_key = 'testkey' From 75050d4bc5ee656c7b33d998107829b6ff138f6d Mon Sep 17 00:00:00 2001 From: Craig Dennis Date: Sun, 20 Nov 2011 17:03:37 +0100 Subject: [PATCH 0017/2940] Simple documentation corrections, mostly typos. --- docs/foreword.rst | 2 +- docs/quickstart.rst | 2 +- docs/signals.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 10b886bf..0f649588 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -44,7 +44,7 @@ object relational mappers, form validation, upload handling, various open authentication technologies and more. Since Flask is based on a very solid foundation there is not a lot of code -in Flask itself. As such it's easy to adapt even for lage applications +in Flask itself. As such it's easy to adapt even for large applications and we are making sure that you can either configure it as much as possible by subclassing things or by forking the entire codebase. If you are interested in that, check out the :ref:`becomingbig` chapter. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 34aa3be4..df97d5ad 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -616,7 +616,7 @@ Storing cookies:: resp.set_cookie('username', 'the username') return resp -Note that cookies are set on response objects. Since you normally you +Note that cookies are set on response objects. Since you normally just return strings from the view functions Flask will convert them into response objects for you. If you explicitly want to do that you can use the :meth:`~flask.make_response` function and then modify it. diff --git a/docs/signals.rst b/docs/signals.rst index 75487800..2d3878f7 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -83,7 +83,7 @@ context are appended to it. Additionally there is a convenient helper method (:meth:`~blinker.base.Signal.connected_to`). that allows you to -temporarily subscribe a function to a signal with is a context manager on +temporarily subscribe a function to a signal with a context manager on its own. Because the return value of the context manager cannot be specified that way one has to pass the list in as argument:: From 9c8f138a42aa43ee8177ed84df5946f8ef6d0c58 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 25 Nov 2011 21:08:19 +0100 Subject: [PATCH 0018/2940] Improved test coverage for the test client --- flask/testsuite/testing.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index f9ee909b..1170b306 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -53,24 +53,32 @@ class TestToolsTestCase(FlaskTestCase): @app.route('/', methods=['GET', 'POST']) def index(): if flask.request.method == 'POST': - return flask.redirect('/redirect') + return flask.redirect('/getsession') flask.session['data'] = 'foo' return 'index' - @app.route('/redirect') - def redirect(): - return 'redirect' + @app.route('/getsession') + def get_session(): + return flask.session.get('data', '') with app.test_client() as c: - ctx = app.test_request_context() - ctx.push() + rv = c.get('/getsession') + assert rv.data == '' + rv = c.get('/') assert rv.data == 'index' assert flask.session.get('data') == 'foo' rv = c.post('/', data={}, follow_redirects=True) - assert rv.data == 'redirect' - assert flask.session.get('data') == 'foo' - ctx.pop() + assert rv.data == 'foo' + + # XXX: Currently the test client does not support + # keeping the context around if a redirect is followed. + # This would be nice to fix but right now the Werkzeug + # test client does not support that. + ##assert flask.session.get('data') == 'foo' + + rv = c.get('/getsession') + assert rv.data == 'foo' def test_session_transactions(self): app = flask.Flask(__name__) @@ -113,6 +121,7 @@ class TestToolsTestCase(FlaskTestCase): with app.test_client() as c: rv = c.get('/') req = flask.request._get_current_object() + self.assert_(req is not None) with c.session_transaction(): self.assert_(req is flask.request._get_current_object()) From 4fe80c05c6f3fc24f145f34e2bc6ce7fbe9f21ad Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 28 Nov 2011 22:47:36 +0100 Subject: [PATCH 0019/2940] Not yet stable? Bollocks --- docs/_templates/sidebarintro.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 164c8745..850fe86a 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -1,9 +1,7 @@

About Flask

Flask is a micro webdevelopment framework for Python. You are currently - looking at the documentation of the development version. Things are - not stable yet, but if you have some feedback, - let me know. + looking at the documentation of the development version.

Other Formats

From c90858a95d5fbe474ba075c3221de0b6f38c9ef2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 28 Nov 2011 22:48:14 +0100 Subject: [PATCH 0020/2940] Added a branch to test functionality enabled by new test client --- flask/testsuite/testing.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 1170b306..0e6feb60 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -71,11 +71,9 @@ class TestToolsTestCase(FlaskTestCase): rv = c.post('/', data={}, follow_redirects=True) assert rv.data == 'foo' - # XXX: Currently the test client does not support - # keeping the context around if a redirect is followed. - # This would be nice to fix but right now the Werkzeug - # test client does not support that. - ##assert flask.session.get('data') == 'foo' + # This support requires a new Werkzeug version + if not hasattr(c, 'redirect_client'): + assert flask.session.get('data') == 'foo' rv = c.get('/getsession') assert rv.data == 'foo' From a9726c43aca2cf7daed3b3690b060494911e72c5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 4 Dec 2011 18:59:37 -0500 Subject: [PATCH 0021/2940] Updated mod_wsgi docs to reference the sys.path hackery --- docs/deploying/mod_wsgi.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index c85ed64f..c4cd3d61 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -61,7 +61,12 @@ Store that file somewhere that you will find it again (e.g.: `/var/www/yourapplication`) and make sure that `yourapplication` and all the libraries that are in use are on the python load path. If you don't want to install it system wide consider using a `virtual python`_ -instance. +instance. Keep in mind that you will have to actually install your +application into the virtualenv as well. Alternatively there is the +option to just patch the path in the `.wsgi` file before the import:: + + import sys + sys.path.insert(0, '/path/to/the/application') Configuring Apache ------------------ From 7ccca13bbdfcbab126722c050dc4ea3d8fa56dc3 Mon Sep 17 00:00:00 2001 From: FND Date: Sat, 24 Dec 2011 10:55:52 +0100 Subject: [PATCH 0022/2940] Fixed various stylistic issues in documentation --- docs/foreword.rst | 28 +++++------ docs/installation.rst | 18 +++---- docs/quickstart.rst | 113 +++++++++++++++++++++--------------------- 3 files changed, 79 insertions(+), 80 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 0f649588..7678d014 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -9,16 +9,16 @@ What does "micro" mean? ----------------------- To me, the "micro" in microframework refers not only to the simplicity and -small size of the framework, but also the fact that it does not make much +small size of the framework, but also the fact that it does not make many decisions for you. While Flask does pick a templating engine for you, we won't make such decisions for your datastore or other parts. -For us however the term “micro” does not mean that the whole implementation +However, to us the term “micro” does not mean that the whole implementation has to fit into a single Python file. One of the design decisions with Flask was that simple tasks should be -simple and not take up a lot of code and yet not limit yourself. Because -of that we took a few design choices that some people might find +simple; they should not take a lot of code and yet they should not limit you. +Because of that we made a few design choices that some people might find surprising or unorthodox. For example, Flask uses thread-local objects internally so that you don't have to pass objects around from function to function within a request in order to stay threadsafe. While this is a @@ -30,17 +30,17 @@ and provide you with a lot of tools to make it as pleasant as possible to work with them. Flask is also based on convention over configuration, which means that -many things are preconfigured. For example, by convention, templates and -static files are in subdirectories within the Python source tree of the -application. While this can be changed you usually don't have to. +many things are preconfigured. For example, by convention templates and +static files are stored in subdirectories within the application's Python source tree. +While this can be changed you usually don't have to. -The main reason however why Flask is called a "microframework" is the idea +The main reason Flask is called a "microframework" is the idea to keep the core simple but extensible. There is no database abstraction layer, no form validation or anything else where different libraries -already exist that can handle that. However Flask knows the concept of -extensions that can add this functionality into your application as if it +already exist that can handle that. However Flask supports +extensions to add such functionality to your application as if it was implemented in Flask itself. There are currently extensions for -object relational mappers, form validation, upload handling, various open +object-relational mappers, form validation, upload handling, various open authentication technologies and more. Since Flask is based on a very solid foundation there is not a lot of code @@ -71,7 +71,7 @@ cause security problems. The documentation will warn you about aspects of web development that require attention to security. Some of these security concerns are far more complex than one might think, and we all sometimes underestimate -the likelihood that a vulnerability will be exploited, until a clever +the likelihood that a vulnerability will be exploited - until a clever attacker figures out a way to exploit our applications. And don't think that your application is not important enough to attract an attacker. Depending on the kind of attack, chances are that automated bots are @@ -88,8 +88,8 @@ 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 us 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 for the unicode differences in Python3. +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. Werkzeug and Flask will be ported to Python 3 as soon as a solution for the changes is found, and we will provide helpful tips how to upgrade diff --git a/docs/installation.rst b/docs/installation.rst index eb645bdc..55065b6d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,8 +9,8 @@ Werkzeug is a toolkit for WSGI, the standard Python interface between web applications and a variety of servers for both development and deployment. Jinja2 renders templates. -So how do you get all that on your computer quickly? There are many ways -which this section will explain, but the most kick-ass method is +So how do you get all that on your computer quickly? There are many ways, +as this section will explain, but the most kick-ass method is virtualenv, so let's look at that first. Either way, you will need Python 2.5 or higher to get started, so be sure @@ -58,7 +58,7 @@ even in your package manager. If you use Ubuntu, try:: If you are on Windows and don't have the `easy_install` command, you must install it first. Check the :ref:`windows-easy-install` section for more -information about how to do that. Once you have it installed, run the +information on how to do that. Once you have it installed, run the same commands as above, but without the `sudo` prefix. Once you have virtualenv installed, just fire up a shell and create @@ -78,7 +78,7 @@ the corresponding environment. On OS X and Linux, do the following:: (Note the space between the dot and the script name. The dot means that this script should run in the context of the current shell. If this command -does not work in your shell, try replacing the dot with ``source``) +does not work in your shell, try replacing the dot with ``source``.) If you are a Windows user, the following command is for you:: @@ -95,15 +95,15 @@ your virtualenv:: A few seconds later you are good to go. -System Wide Installation +System-Wide Installation ------------------------ -This is possible as well, but I do not recommend it. Just run -`easy_install` with root rights:: +This is possible as well, though I do not recommend it. Just run +`easy_install` with root privileges:: $ sudo easy_install Flask -(Run it in an Admin shell on Windows systems and without `sudo`). +(Run it in an Admin shell on Windows systems and without `sudo`.) Living on the Edge @@ -159,7 +159,7 @@ to the `PATH` environment variable. To do that, right-click on the "Computer" icon on the Desktop or in the Start menu, and choose "Properties". Then, on Windows Vista and Windows 7 click on "Advanced System settings"; on Windows XP, click on the "Advanced" tab instead. Then click -on the "Environment variables" button and double click on the "Path" +on the "Environment variables" button and double-click on the "Path" variable in the "System variables" section. There append the path of your Python interpreter's Scripts folder; make sure you delimit it from existing values with a semicolon. Assuming you are using Python 2.6 on diff --git a/docs/quickstart.rst b/docs/quickstart.rst index df97d5ad..9b40dd13 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,7 +3,7 @@ Quickstart ========== -Eager to get started? This page gives a good introduction in how to get +Eager to get started? This page gives a good introduction on how to get started with Flask. This assumes you already have Flask installed. If you do not, head over to the :ref:`installation` section. @@ -39,16 +39,16 @@ So what did that code do? 1. First we imported the :class:`~flask.Flask` class. An instance of this class will be our WSGI application. The first argument is the name of - the application's module. If you are using a single module (like here) - you should use `__name__` because depending on if it's started as + the application's module. If you are using a single module (as in this example) + you should use `__name__` because depending on whether it's started as application or imported as module the name will be different (``'__main__'`` versus the actual import name). For more information - on that, have a look at the :class:`~flask.Flask` documentation. + on this technique, have a look at the :class:`~flask.Flask` documentation. 2. Next we create an instance of it. We pass it the name of the module / package. This is needed so that Flask knows where it should look for templates, static files and so on. 3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask - what URL should trigger our function. + which URL should trigger our function. 4. The function then has a name which is also used to generate URLs to that particular function, and returns the message we want to display in the user's browser. @@ -63,15 +63,14 @@ To stop the server, hit control-C. .. admonition:: Externally Visible Server - If you run the server you will notice that the server is only available + If you run the server you will notice that the server is only accessible from your own computer, not from any other in the network. This is the default because in debugging mode a user of the application can execute - arbitrary Python code on your computer. If you have `debug` disabled - or trust the users on your network, you can make the server publicly - available. + arbitrary Python code on your computer. - Just change the call of the :meth:`~flask.Flask.run` method to look - like this:: + If you have `debug` disabled or trust the users on your network, you can + make the server publicly available simply by changing the call of the + :meth:`~flask.Flask.run` method to look like this:: app.run(host='0.0.0.0') @@ -83,9 +82,9 @@ Debug Mode The :meth:`~flask.Flask.run` method is nice to start a local development server, but you would have to restart it manually after each -change you do to code. That is not very nice and Flask can do better. If -you enable the debug support the server will reload itself on code changes -and also provide you with a helpful debugger if things go wrong. +change to your code. That is not very nice and Flask can do better. If +you enable debug support the server will reload itself on code changes, +and it will also provide you with a helpful debugger if things go wrong. There are two ways to enable debugging. Either set that flag on the application object:: @@ -93,7 +92,7 @@ application object:: app.debug = True app.run() -Or pass it to run:: +Or pass it to `run`:: app.run(debug=True) @@ -123,7 +122,7 @@ Routing ------- Modern web applications have beautiful URLs. This helps people remember -the URLs which is especially handy for applications that are used from +the URLs, which is especially handy for applications that are used from mobile devices with slower network connections. If the user can directly go to the desired page without having to hit the index page it is more likely they will like the page and come back next time. @@ -171,8 +170,8 @@ The following converters exist: .. admonition:: Unique URLs / Redirection Behaviour Flask's URL rules are based on Werkzeug's routing module. The idea - behind that module is to ensure nice looking and also unique URLs based - on behaviour Apache and earlier servers coined. + behind that module is to ensure nice-looking and also unique URLs based + on behaviour coined by Apache and earlier servers. Take these two rules:: @@ -190,14 +189,14 @@ The following converters exist: that sense. Accessing it without a trailing slash will cause Flask to redirect to the canonical URL with the trailing slash. - However in the second case the URL is defined without a slash so it + However, in the second case the URL is defined without a slash so it behaves similar to a file and accessing the URL with a trailing slash - will be a 404 error. + will result in a 404 error. Why is this? This allows relative URLs to continue working if users access the page when they forget a trailing slash. This behaviour is also consistent with how Apache and other servers work. Also, the URLs - will stay unique which helps search engines not indexing the same page + will stay unique which ensures search engines do not index the same page twice. @@ -238,7 +237,7 @@ parameter. Here are some examples: (This also uses the :meth:`~flask.Flask.test_request_context` method explained below. It basically tells Flask to think we are handling a request even though we are not, we are in an interactive Python shell. -Have a look at the explanation below. :ref:`context-locals`). +Have a look at the explanation below: :ref:`context-locals`.) Why would you want to build URLs instead of hardcoding them in your templates? There are three good reasons for this: @@ -270,10 +269,10 @@ that can be changed by providing the `methods` argument to the If `GET` is present, `HEAD` will be added automatically for you. You don't have to deal with that. It will also make sure that `HEAD` requests -are handled like the `HTTP RFC`_ (the document describing the HTTP +are handled as the `HTTP RFC`_ (the document describing the HTTP protocol) demands, so you can completely ignore that part of the HTTP -specification. Likewise as of Flask 0.6, `OPTIONS` is implemented for you -as well automatically. +specification. Likewise, as of Flask 0.6, `OPTIONS` is implemented for you +automatically as well. You have no idea what an HTTP method is? Worry not, here is a quick introduction to HTTP methods and why they matter: @@ -297,14 +296,14 @@ very common: `POST` The browser tells the server that it wants to *post* some new information to that URL and that the server must ensure the data is - stored and only stored once. This is how HTML forms are usually - transmitting data to the server. + stored and only stored once. This is how HTML forms usually + transmit data to the server. `PUT` Similar to `POST` but the server might trigger the store procedure multiple times by overwriting the old values more than once. Now you - might be asking why is this useful, but there are some good reasons - to do it this way. Consider that the connection gets lost during + might be asking why this is useful, but there are some good reasons + to do it this way. Consider that the connection is lost during transmission: in this situation a system between the browser and the server might receive the request safely a second time without breaking things. With `POST` that would not be possible because it must only @@ -330,13 +329,13 @@ use it. Static Files ------------ -Dynamic web applications need static files as well. That's usually where +Dynamic web applications also need static files. That's usually where the CSS and JavaScript files are coming from. Ideally your web server is configured to serve them for you, but during development Flask can do that as well. Just create a folder called `static` in your package or next to your module and it will be available at `/static` on the application. -To generate URLs to that part of the URL, use the special ``'static'`` URL +To generate URLs that part of the URL, use the special ``'static'`` URL name:: url_for('static', filename='style.css') @@ -352,7 +351,7 @@ the application secure. Because of that Flask configures the `Jinja2 `_ template engine for you automatically. To render a template you can use the :func:`~flask.render_template` -method. All you have to do is to provide the name of the template and the +method. All you have to do is provide the name of the template and the variables you want to pass to the template engine as keyword arguments. Here's a simple example of how to render a template:: @@ -364,7 +363,7 @@ Here's a simple example of how to render a template:: return render_template('hello.html', name=name) Flask will look for templates in the `templates` folder. So if your -application is a module, that folder is next to that module, if it's a +application is a module, this folder is next to that module, if it's a package it's actually inside your package: **Case 1**: a module:: @@ -405,9 +404,9 @@ know how that works, head over to the :ref:`template-inheritance` pattern documentation. Basically template inheritance makes it possible to keep certain elements on each page (like header, navigation and footer). -Automatic escaping is enabled, so if name contains HTML it will be escaped +Automatic escaping is enabled, so if `name` contains HTML it will be escaped automatically. If you can trust a variable and you know that it will be -safe HTML (because for example it came from a module that converts wiki +safe HTML (for example because it came from a module that converts wiki markup to HTML) you can mark it as safe by using the :class:`~jinja2.Markup` class or by using the ``|safe`` filter in the template. Head over to the Jinja 2 documentation for more examples. @@ -442,7 +441,7 @@ For web applications it's crucial to react to the data a client sent to the server. In Flask this information is provided by the global :class:`~flask.request` object. If you have some experience with Python you might be wondering how that object can be global and how Flask -manages to still be threadsafe. The answer are context locals: +manages to still be threadsafe. The answer is context locals: .. _context-locals: @@ -460,20 +459,20 @@ These objects are actually proxies to objects that are local to a specific context. What a mouthful. But that is actually quite easy to understand. Imagine the context being the handling thread. A request comes in and the -webserver decides to spawn a new thread (or something else, the -underlying object is capable of dealing with other concurrency systems -than threads as well). When Flask starts its internal request handling it +web server decides to spawn a new thread (or something else, the +underlying object is capable of dealing with concurrency systems other +than threads). When Flask starts its internal request handling it figures out that the current thread is the active context and binds the current application and the WSGI environments to that context (thread). -It does that in an intelligent way that one application can invoke another +It does that in an intelligent way so that one application can invoke another application without breaking. So what does this mean to you? Basically you can completely ignore that -this is the case unless you are doing something like unittesting. You -will notice that code that depends on a request object will suddenly break +this is the case unless you are doing something like unit testing. You +will notice that code which depends on a request object will suddenly break because there is no request object. The solution is creating a request object yourself and binding it to the context. The easiest solution for -unittesting is by using the :meth:`~flask.Flask.test_request_context` +unit testing is to use the :meth:`~flask.Flask.test_request_context` context manager. In combination with the `with` statement it will bind a test request so that you can interact with it. Here is an example:: @@ -680,8 +679,8 @@ converting return values into response objects is as follows: default parameters. 3. If a tuple is returned the response object is created by passing the tuple as arguments to the response object's constructor. -4. If neither of that works, Flask will assume the return value is a - valid WSGI application and converts that into a response object. +4. If none of that works, Flask will assume the return value is a + valid WSGI application and convert that into a response object. If you want to get hold of the resulting response object inside the view you can use the :func:`~flask.make_response` function. @@ -711,8 +710,8 @@ return it: Sessions -------- -Besides the request object there is also a second object called -:class:`~flask.session` that allows you to store information specific to a +In addition to the request object there is also a second object called +:class:`~flask.session` which allows you to store information specific to a user from one request to the next. This is implemented on top of cookies for you and signs the cookies cryptographically. What this means is that the user could look at the contents of your cookie but not modify it, @@ -752,12 +751,12 @@ sessions work:: # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' -The here mentioned :func:`~flask.escape` does escaping for you if you are -not using the template engine (like in this example). +The :func:`~flask.escape` mentioned here does escaping for you if you are +not using the template engine (as in this example). .. admonition:: How to generate good secret keys - The problem with random is that it's hard to judge what random is. And + The problem with random is that it's hard to judge what is truly random. And a secret key should be as random as possible. Your operating system has ways to generate pretty random stuff based on a cryptographic random generator which can be used to get such a key: @@ -775,9 +774,9 @@ Good applications and user interfaces are all about feedback. If the user does not get enough feedback they will probably end up hating the 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. +possible to record a message at the end of a request and access it on the next +(and only the next) request. This is usually combined with a layout +template to expose the message. To flash a message use the :func:`~flask.flash` method, to get hold of the messages you can use :func:`~flask.get_flashed_messages` which is also @@ -790,10 +789,10 @@ Logging .. versionadded:: 0.3 Sometimes you might be in a situation where you deal with data that -should be correct, but actually is not. For example you may have some client -side code that sends an HTTP request to the server but it's obviously -malformed. This might be caused by a user tempering with the data, or the -client code failing. Most of the time, it's okay to reply with ``400 Bad +should be correct, but actually is not. For example you may have some client-side +code that sends an HTTP request to the server but it's obviously +malformed. This might be caused by a user tampering with the data, or the +client code failing. Most of the time it's okay to reply with ``400 Bad Request`` in that situation, but sometimes that won't do and the code has to continue working. From 065afe53a6f72e42428c4373ed10d082868f25d6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 27 Dec 2011 19:08:44 +0100 Subject: [PATCH 0023/2940] Improved doc exaples. This fixes #370 and #371. --- docs/quickstart.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index df97d5ad..fb7de08c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -153,12 +153,12 @@ rule with ````. Here are some nice examples:: @app.route('/user/') def show_user_profile(username): # show the user profile for that user - pass + return 'User %s' % username @app.route('/post/') def show_post(post_id): # show the post with the given id, the id is an integer - pass + return 'Post %d' % post_id The following converters exist: @@ -178,11 +178,11 @@ The following converters exist: @app.route('/projects/') def projects(): - pass + return 'The project page' @app.route('/about') def about(): - pass + return 'The about page' They look rather similar, the difference is the trailing slash in the URL *definition*. In the first case, the canonical URL for the From b6625ec19329031ddb26dec63c6bd0477a00bf22 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 5 Jan 2012 00:04:50 +0100 Subject: [PATCH 0024/2940] Fixed a wrong example in the view docs. This fixes #374 --- docs/views.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/views.rst b/docs/views.rst index a48d81f2..092bb937 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -223,7 +223,7 @@ registration code:: app.add_url_rule(url, defaults={pk: None}, view_func=view_func, methods=['GET',]) app.add_url_rule(url, view_func=view_func, methods=['POST',]) - app.add_url_rule('%s<%s:%s>' % (url, pk, pk_type), view_func=view_func, + app.add_url_rule('%s<%s:%s>' % (url, pk_type, pk), view_func=view_func, methods=['GET', 'PUT', 'DELETE']) register_api(UserAPI, 'user_api', '/users/', pk='user_id') From d50a0b495566fa39ab30c79848dee3bdb3457201 Mon Sep 17 00:00:00 2001 From: awsum Date: Sat, 7 Jan 2012 21:11:35 +0200 Subject: [PATCH 0025/2940] typo fix --- docs/patterns/sqlalchemy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 5a33d1f6..270fa79e 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -157,7 +157,7 @@ Here is an example table and model (put this into `models.py`):: self.email = email def __repr__(self): - return '' % (self.name, self.email) + return '' % (self.name) users = Table('users', metadata, Column('id', Integer, primary_key=True), From 26a9c2079d77aba93dde6f95472f3d89d2d9783d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 7 Jan 2012 17:50:11 -0500 Subject: [PATCH 0026/2940] Add test to catch imports at Flask instantiation. --- flask/testsuite/helpers.py | 11 +++++++++++ flask/testsuite/test_apps/importerror.py | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 flask/testsuite/test_apps/importerror.py diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 41e31be9..ee365605 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -331,10 +331,21 @@ class LoggingTestCase(FlaskTestCase): '/myview/create') +class NoImportsTestCase(FlaskTestCase): + "Test Flasks are created without __import__." + + def test_name_with_import_error(self): + try: + flask.Flask('importerror') + except NotImplementedError: + self.fail('Flask(import_name) is importing import_name.') + + def suite(): suite = unittest.TestSuite() if flask.json_available: suite.addTest(unittest.makeSuite(JSONTestCase)) suite.addTest(unittest.makeSuite(SendfileTestCase)) suite.addTest(unittest.makeSuite(LoggingTestCase)) + suite.addTest(unittest.makeSuite(NoImportsTestCase)) return suite diff --git a/flask/testsuite/test_apps/importerror.py b/flask/testsuite/test_apps/importerror.py new file mode 100644 index 00000000..eb298b9b --- /dev/null +++ b/flask/testsuite/test_apps/importerror.py @@ -0,0 +1,2 @@ +# NoImportsTestCase +raise NotImplementedError From d16491145dc553bc30aa9c71fb3647fda8d23534 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 6 Jan 2012 11:48:14 -0500 Subject: [PATCH 0027/2940] Update module helpers to avoid Python imports. This avoids errors in creating Flask instances where there are import errors in the module or package matching the import name. Those runtime errors will be apparent to the user soon enough, but tools which build Flask instances meta-programmatically benefit from a Flask which does not __import__. --- flask/helpers.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 7295dc3c..456f30cd 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -11,8 +11,10 @@ from __future__ import with_statement +import imp import os import sys +import pkgutil import posixpath import mimetypes from time import time @@ -492,14 +494,12 @@ def get_root_path(import_name): Not to be confused with the package path returned by :func:`find_package`. """ - __import__(import_name) - try: - directory = os.path.dirname(sys.modules[import_name].__file__) - return os.path.abspath(directory) - except AttributeError: - # this is necessary in case we are running from the interactive - # python shell. It will never be used for production code however + loader = pkgutil.get_loader(import_name) + if loader is None: return os.getcwd() + filepath = os.path.abspath(loader.get_filename(import_name)) + # filepath for import_name.py for a module, or __init__.py for a package. + return os.path.dirname(filepath) def find_package(import_name): @@ -510,16 +510,17 @@ def find_package(import_name): import the module. The prefix is the path below which a UNIX like folder structure exists (lib, share etc.). """ - __import__(import_name) - root_mod = sys.modules[import_name.split('.')[0]] - package_path = getattr(root_mod, '__file__', None) - if package_path is None: + root_mod_name = import_name.split('.')[0] + loader = pkgutil.get_loader(root_mod_name) + if loader is not None: + filename = loader.get_filename(root_mod_name) + package_path = os.path.abspath(os.path.dirname(filename)) + # package_path ends with __init__.py for a package + if loader.is_package(root_mod_name): + package_path = os.path.dirname(package_path) + else: # support for the interactive python shell package_path = os.getcwd() - else: - package_path = os.path.abspath(os.path.dirname(package_path)) - if hasattr(root_mod, '__path__'): - package_path = os.path.dirname(package_path) # leave the egg wrapper folder or the actual .egg on the filesystem test_package_path = package_path From a3b30b7e3b026e821716be63d586e078eaa6ee7f Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 7 Jan 2012 17:54:37 -0500 Subject: [PATCH 0028/2940] Handle zip & interactive cases in module helpers. --- flask/helpers.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 456f30cd..48748dff 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -495,11 +495,18 @@ def get_root_path(import_name): Not to be confused with the package path returned by :func:`find_package`. """ loader = pkgutil.get_loader(import_name) - if loader is None: + if loader is None or import_name == '__main__': + # import name is not found, or interactive/main module return os.getcwd() - filepath = os.path.abspath(loader.get_filename(import_name)) - # filepath for import_name.py for a module, or __init__.py for a package. - return os.path.dirname(filepath) + # For .egg, zipimporter does not have get_filename until Python 2.7. + if hasattr(loader, 'get_filename'): + filepath = loader.get_filename(import_name) + else: + # Fall back to imports. + __import__(import_name) + filepath = sys.modules[import_name].__file__ + # filepath is import_name.py for a module, or __init__.py for a package. + return os.path.dirname(os.path.abspath(filepath)) def find_package(import_name): @@ -512,24 +519,25 @@ def find_package(import_name): """ root_mod_name = import_name.split('.')[0] loader = pkgutil.get_loader(root_mod_name) - if loader is not None: - filename = loader.get_filename(root_mod_name) + if loader is None or import_name == '__main__': + # import name is not found, or interactive/main module + package_path = os.getcwd() + else: + # For .egg, zipimporter does not have get_filename until Python 2.7. + if hasattr(loader, 'get_filename'): + filename = loader.get_filename(root_mod_name) + else: + # zipimporter's loader.archive points to the .egg or .zip + # archive filename is dropped in call to dirname below. + filename = loader.archive package_path = os.path.abspath(os.path.dirname(filename)) # package_path ends with __init__.py for a package if loader.is_package(root_mod_name): package_path = os.path.dirname(package_path) - else: - # support for the interactive python shell - package_path = os.getcwd() - # leave the egg wrapper folder or the actual .egg on the filesystem - test_package_path = package_path - if os.path.basename(test_package_path).endswith('.egg'): - test_package_path = os.path.dirname(test_package_path) - - site_parent, site_folder = os.path.split(test_package_path) + site_parent, site_folder = os.path.split(package_path) py_prefix = os.path.abspath(sys.prefix) - if test_package_path.startswith(py_prefix): + if package_path.startswith(py_prefix): return py_prefix, package_path elif site_folder.lower() == 'site-packages': parent, folder = os.path.split(site_parent) From fde6e364a497cd44d049df17c93d1fd05ec09f90 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 7 Jan 2012 17:55:02 -0500 Subject: [PATCH 0029/2940] Update tests for new module helpers. --- flask/testsuite/config.py | 115 ++++++++++-------- .../lib/python2.5/site-packages/site_app.py | 3 + .../site-packages/site_package/__init__.py | 3 + flask/testsuite/test_apps/main_app.py | 4 + .../path/installed_package/__init__.py | 3 + 5 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 flask/testsuite/test_apps/lib/python2.5/site-packages/site_app.py create mode 100644 flask/testsuite/test_apps/lib/python2.5/site-packages/site_package/__init__.py create mode 100644 flask/testsuite/test_apps/main_app.py create mode 100644 flask/testsuite/test_apps/path/installed_package/__init__.py diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index ad1721fd..e0f7005f 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -98,6 +98,14 @@ class InstanceTestCase(FlaskTestCase): app = flask.Flask(__name__, instance_path=here) self.assert_equal(app.instance_path, here) + def test_main_module_paths(self): + # Test an app with '__main__' as the import name, uses cwd. + from main_app import app + here = os.path.abspath(os.getcwd()) + self.assert_equal(app.instance_path, os.path.join(here, 'instance')) + if 'main_app' in sys.modules: + del sys.modules['main_app'] + def test_uninstalled_module_paths(self): from config_module_app import app here = os.path.abspath(os.path.dirname(__file__)) @@ -109,70 +117,75 @@ class InstanceTestCase(FlaskTestCase): self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance')) def test_installed_module_paths(self): - import types - expected_prefix = os.path.abspath('foo') - mod = types.ModuleType('myapp') - mod.__file__ = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'myapp.py') - sys.modules['myapp'] = mod + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + sys.path.append(site_packages) try: - mod.app = flask.Flask(mod.__name__) - self.assert_equal(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) + import site_app + self.assert_equal(site_app.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_app-instance')) finally: - sys.modules['myapp'] = None + sys.prefix = real_prefix + sys.path.remove(site_packages) + if 'site_app' in sys.modules: + del sys.modules['site_app'] def test_installed_package_paths(self): - import types - expected_prefix = os.path.abspath('foo') - package_path = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'myapp') - mod = types.ModuleType('myapp') - mod.__path__ = [package_path] - mod.__file__ = os.path.join(package_path, '__init__.py') - sys.modules['myapp'] = mod + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + installed_path = os.path.join(expected_prefix, 'path') + sys.path.append(installed_path) try: - mod.app = flask.Flask(mod.__name__) - self.assert_equal(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) + import installed_package + self.assert_equal(installed_package.app.instance_path, + os.path.join(expected_prefix, 'var', + 'installed_package-instance')) finally: - sys.modules['myapp'] = None + sys.prefix = real_prefix + sys.path.remove(installed_path) + if 'installed_package' in sys.modules: + del sys.modules['installed_package'] - def test_prefix_installed_paths(self): - import types - expected_prefix = os.path.abspath(sys.prefix) - package_path = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'myapp') - mod = types.ModuleType('myapp') - mod.__path__ = [package_path] - mod.__file__ = os.path.join(package_path, '__init__.py') - sys.modules['myapp'] = mod + def test_prefix_package_paths(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + sys.path.append(site_packages) try: - mod.app = flask.Flask(mod.__name__) - self.assert_equal(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) + import site_package + self.assert_equal(site_package.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_package-instance')) finally: - sys.modules['myapp'] = None + sys.prefix = real_prefix + sys.path.remove(site_packages) + if 'site_package' in sys.modules: + del sys.modules['site_package'] def test_egg_installed_paths(self): - import types - expected_prefix = os.path.abspath(sys.prefix) - package_path = os.path.join(expected_prefix, 'lib', 'python2.5', - 'site-packages', 'MyApp.egg', 'myapp') - mod = types.ModuleType('myapp') - mod.__path__ = [package_path] - mod.__file__ = os.path.join(package_path, '__init__.py') - sys.modules['myapp'] = mod + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + egg_path = os.path.join(site_packages, 'SiteEgg.egg') + sys.path.append(site_packages) + sys.path.append(egg_path) try: - mod.app = flask.Flask(mod.__name__) - self.assert_equal(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) + import site_egg # in SiteEgg.egg + self.assert_equal(site_egg.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_egg-instance')) finally: - sys.modules['myapp'] = None + sys.prefix = real_prefix + sys.path.remove(site_packages) + sys.path.remove(egg_path) + if 'site_egg' in sys.modules: + del sys.modules['site_egg'] def suite(): diff --git a/flask/testsuite/test_apps/lib/python2.5/site-packages/site_app.py b/flask/testsuite/test_apps/lib/python2.5/site-packages/site_app.py new file mode 100644 index 00000000..06271108 --- /dev/null +++ b/flask/testsuite/test_apps/lib/python2.5/site-packages/site_app.py @@ -0,0 +1,3 @@ +import flask + +app = flask.Flask(__name__) diff --git a/flask/testsuite/test_apps/lib/python2.5/site-packages/site_package/__init__.py b/flask/testsuite/test_apps/lib/python2.5/site-packages/site_package/__init__.py new file mode 100644 index 00000000..06271108 --- /dev/null +++ b/flask/testsuite/test_apps/lib/python2.5/site-packages/site_package/__init__.py @@ -0,0 +1,3 @@ +import flask + +app = flask.Flask(__name__) diff --git a/flask/testsuite/test_apps/main_app.py b/flask/testsuite/test_apps/main_app.py new file mode 100644 index 00000000..a5f372c8 --- /dev/null +++ b/flask/testsuite/test_apps/main_app.py @@ -0,0 +1,4 @@ +import flask + +# Test Flask initialization with main module. +app = flask.Flask('__main__') diff --git a/flask/testsuite/test_apps/path/installed_package/__init__.py b/flask/testsuite/test_apps/path/installed_package/__init__.py new file mode 100644 index 00000000..06271108 --- /dev/null +++ b/flask/testsuite/test_apps/path/installed_package/__init__.py @@ -0,0 +1,3 @@ +import flask + +app = flask.Flask(__name__) From 970de5e8b67b6c5bbaac082e888f8ebc6e16cfb7 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 9 Jan 2012 10:25:06 -0500 Subject: [PATCH 0030/2940] Add discussion to NoImportsTestCase. --- flask/testsuite/helpers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index ee365605..89c6c188 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -332,7 +332,15 @@ class LoggingTestCase(FlaskTestCase): class NoImportsTestCase(FlaskTestCase): - "Test Flasks are created without __import__." + """Test Flasks are created without import. + + Avoiding ``__import__`` helps create Flask instances where there are errors + at import time. Those runtime errors will be apparent to the user soon + enough, but tools which build Flask instances meta-programmatically benefit + from a Flask which does not ``__import__``. Instead of importing to + retrieve file paths or metadata on a module or package, use the pkgutil and + imp modules in the Python standard library. + """ def test_name_with_import_error(self): try: From 83189f20bf9c3c026e7ecd9565b4373c4fea8d10 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 09:05:42 -0500 Subject: [PATCH 0031/2940] Add .egg for zipimporter instance path test. .gitignore contains '*.egg' --- .../lib/python2.5/site-packages/SiteEgg.egg | Bin 0 -> 1218 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 flask/testsuite/test_apps/lib/python2.5/site-packages/SiteEgg.egg diff --git a/flask/testsuite/test_apps/lib/python2.5/site-packages/SiteEgg.egg b/flask/testsuite/test_apps/lib/python2.5/site-packages/SiteEgg.egg new file mode 100644 index 0000000000000000000000000000000000000000..d80abe9309fb1a9d0c4fce1fd76e87679282a012 GIT binary patch literal 1218 zcmWIWW@Zs#U|`^2*i@|Uz;wUq#B3n%6cFX;jJGoR?{Kkt3Ux3^zEk*!FGo6E$(OtB+LU}ngynUfdIpE4;jX3_M? zGsB}6MkgkU<)*?p_uOI9m8szL6tXEP|qV1=vse3_t#S^V_I-b4$ zzW(eRn;Ub&6@*V*J$uIc^mYCZW!JxcDgJzX3gm$)b%*)?RnOF zUC;TGXHM%KTenJOzk;3mRvlAz6<#!VOlG@o#0<2b5r}1x-I0=7keZj0nwMM|pOcxF zT?}&2V48(%Sx&yo+-udjS>*?dE<9UVGTUYDcne&^246YbI_ymlm ztw9_>UzPg$KX;nQrFZtMugTxS3sC#m^5x3WYvV$zlMMB<}K6MufDxtZLf6euYpt#}$Vlia* zmE;%1=cJaU=77T)8obZZy@wn)DBi2eEYK}ZOiKj^2qTjSGw!qwv=t1NG=eDPlpo-Y z(1|U9LG&>&ENMK7suNiwwzP!MC=85NY$*%f1Z0nbQUMGsX`GF00=6`PZWMZwKp1rs z*(g{7LD!BRV+ie)Oh|q~i9d8RkbMJ+HW*maxC7Y?SR@8`v$BDdumj;Cpc`KS6)`XX E0770`LI3~& literal 0 HcmV?d00001 From 1f20a11284e3e9bcfc08939c48e3ab590da7c7a4 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 09:23:40 -0500 Subject: [PATCH 0032/2940] Fall back to imports w/exotic pkg loaders, #380. Needs a test, which likely requires introducing a mock library. --- flask/helpers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 48748dff..3c9a5669 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -526,10 +526,17 @@ def find_package(import_name): # For .egg, zipimporter does not have get_filename until Python 2.7. if hasattr(loader, 'get_filename'): filename = loader.get_filename(root_mod_name) - else: + elif hasattr(loader, 'archive'): # zipimporter's loader.archive points to the .egg or .zip # archive filename is dropped in call to dirname below. filename = loader.archive + else: + # At least one loader is missing both get_filename and archive: + # Google App Engine's HardenedModulesHook + # + # Fall back to imports. + __import__(import_name) + filename = sys.modules[import_name].__file__ package_path = os.path.abspath(os.path.dirname(filename)) # package_path ends with __init__.py for a package if loader.is_package(root_mod_name): From a59cbd4f5256a356fcb7858bf0a5a0ad535e8e6d Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Thu, 8 Dec 2011 14:41:29 +0200 Subject: [PATCH 0033/2940] Minor grammar fix --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index f11c3ddd..3e35e46f 100644 --- a/flask/views.py +++ b/flask/views.py @@ -146,5 +146,5 @@ class MethodView(View): # retry with GET if meth is None and request.method == 'HEAD': meth = getattr(self, 'get', None) - assert meth is not None, 'Not implemented method %r' % request.method + assert meth is not None, 'Unimplemented method %r' % request.method return meth(*args, **kwargs) From d18868bd17f612d5598b8e8cc29800ae84c37620 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Thu, 8 Dec 2011 17:36:52 +0200 Subject: [PATCH 0034/2940] Add simple decorator example Github issue #358 --- docs/views.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/views.rst b/docs/views.rst index 092bb937..feee0a8b 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -144,14 +144,22 @@ routing system it does not make much sense to decorate the class itself. Instead you either have to decorate the return value of :meth:`~flask.views.View.as_view` by hand:: - view = rate_limited(UserAPI.as_view('users')) + def user_required(f): + """Checks whether user is logged in or raises error 401.""" + def decorator(*args, **kwargs): + if not g.user: + abort(401) + return f(*args, **kwargs) + return decorator + + view = user_required(UserAPI.as_view('users')) app.add_url_rule('/users/', view_func=view) Starting with Flask 0.8 there is also an alternative way where you can specify a list of decorators to apply in the class declaration:: class UserAPI(MethodView): - decorators = [rate_limited] + decorators = [user_required] Due to the implicit self from the caller's perspective you cannot use regular view decorators on the individual methods of the view however, From e2cb8d2ef195df8ffc76587d136182b431b96983 Mon Sep 17 00:00:00 2001 From: ThomasWaldmann Date: Fri, 30 Sep 2011 14:03:01 +0300 Subject: [PATCH 0035/2940] fixed typo, quote "with" statement --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 61723dca..2fed4b4a 100644 --- a/CHANGES +++ b/CHANGES @@ -70,7 +70,7 @@ Released on September 29th 2011, codename Rakija as defaults. - Added :attr:`flask.views.View.decorators` to support simpler decorating of pluggable (class based) views. -- Fixed an issue where the test client if used with the with statement did not +- Fixed an issue where the test client if used with the "with" statement did not trigger the execution of the teardown handlers. - Added finer control over the session cookie parameters. - HEAD requests to a method view now automatically dispatch to the `get` From 94692607e6247a919f952375d922e930751cadcf Mon Sep 17 00:00:00 2001 From: Sundar Raman Date: Wed, 2 Nov 2011 15:18:04 -0400 Subject: [PATCH 0036/2940] Documentation on how to use external debuggers like eclipse/aptana --- docs/quickstart.rst | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9bf331ae..be3cecdd 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -114,8 +114,40 @@ Screenshot of the debugger in action: .. admonition:: Working With Other Debuggers - Debuggers interfere with each other. If you are using another debugger - (e.g. PyDev or IntelliJ), you may need to set ``app.debug = False``. + Debuggers interfere with each other. + That said, you may still wish to use the debugger in a tool of your choice. + Flask provides the following options to manage the debug process: + + * ``debug`` - whether to enable debug mode and catch exceptinos + * ``use_debugger`` - whether to use the internal Flask debugger + * ``use_reloader`` - whether to reload and fork the process on exception + + ``debug`` must be True (i.e., exceptions must caught) in order for the + other two options to have any value. + + If you're using Aptana/Eclipse for debugging you'll need to set both + ``use_debugger`` and ``use_reloader`` to False. + + A possible useful pattern for configuration is to set the following in your + config.yaml (change the block as approriate for your application, of course):: + + FLASK: + DEBUG: True + DEBUG_WITH_APTANA: True + + Then in your application's entry-point (main.py), you could have something like:: + + if __name__ == "__main__": + # To allow aptana to receive errors, set use_debugger=False + app = create_app(config="config.yaml") + + if app.debug: use_debugger = True + try: + # Disable Flask's debugger if external debugger is requested + use_debugger = not(app.config.get('DEBUG_WITH_APTANA')) + except: + pass + app.run(use_debugger=use_debugger, debug=app.debug, use_reloader=use_debugger, host='0.0.0.0') Routing From d620ea7ea2be35b4757f27fdca49be91d8dfd165 Mon Sep 17 00:00:00 2001 From: awsum Date: Fri, 13 Jan 2012 01:36:15 +0200 Subject: [PATCH 0037/2940] Update docs/api.rst a bit to reflect code. --- docs/api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 47ff84e5..d2b62199 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -146,6 +146,10 @@ Response Objects .. attribute:: status + A string with a response status. + + .. attribute:: status_code + The response status as integer. From 46651659c29a3ff8fa470e32d3e35f9cc2c7fbc0 Mon Sep 17 00:00:00 2001 From: Kyle Wild Date: Sun, 23 Oct 2011 16:41:00 -0700 Subject: [PATCH 0038/2940] Fix a typo ("is"->"if") in the comments; clarify a bit --- flask/ctx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/ctx.py b/flask/ctx.py index 9a72d251..0c2082e3 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -21,9 +21,9 @@ class _RequestGlobals(object): def has_request_context(): """If you have code that wants to test if a request context is there or - not this function can be used. For instance if you want to take advantage - of request information is it's available but fail silently if the request - object is unavailable. + not this function can be used. For instance, if you want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. :: From 8532bd51a709263907a4604660b8dabf51e39ab2 Mon Sep 17 00:00:00 2001 From: Kyle Wild Date: Sun, 23 Oct 2011 16:41:59 -0700 Subject: [PATCH 0039/2940] [docstring] Remove an extra `if` clause to clarify sentence --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 0c2082e3..47ac0cc1 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -21,7 +21,7 @@ class _RequestGlobals(object): def has_request_context(): """If you have code that wants to test if a request context is there or - not this function can be used. For instance, if you want to take advantage + not this function can be used. For instance, you may want to take advantage of request information if the request object is available, but fail silently if it is unavailable. From 2a4d3ef1168e0ea1733898527229961c9ddafced Mon Sep 17 00:00:00 2001 From: Reisen Date: Fri, 8 Jul 2011 15:46:16 +0100 Subject: [PATCH 0040/2940] Added a template filter decorator to blueprints. --- flask/blueprints.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/flask/blueprints.py b/flask/blueprints.py index ccdda38d..54fa33bc 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -185,6 +185,17 @@ class Blueprint(_PackageBoundObject): return f return decorator + def app_template_filter(self, name = None): + """Like :meth:`Flask.template_filter` but for a blueprint. The filter + is available for the entire application. + """ + def decorator(f): + def register_template(state): + state.app.jinja_env.filters[name or f.__name__] = f + self.record_once(register_template) + return f + return decorator + def before_request(self, f): """Like :meth:`Flask.before_request` but for a blueprint. This function is only executed before each request that is handled by a function of From ce4d589d5b7bbda46b9c34211dcd3e1c90ee83c9 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 20:16:48 -0500 Subject: [PATCH 0041/2940] Add non-decorator template filter methods. Suggested by @Poincare on GitHub, on @Reisen's pull request: https://github.com/mitsuhiko/flask/pull/272 --- flask/app.py | 12 +++++++++++- flask/blueprints.py | 25 +++++++++++++++++++------ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/flask/app.py b/flask/app.py index 42ffea4d..0e462020 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1018,10 +1018,20 @@ class Flask(_PackageBoundObject): function name will be used. """ def decorator(f): - self.jinja_env.filters[name or f.__name__] = f + self.add_template_filter(f, name=name) return f return decorator + @setupmethod + def add_template_filter(self, f, name=None): + """Register a custom template filter. Works exactly like the + :meth:`template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + self.jinja_env.filters[name or f.__name__] = f + @setupmethod def before_request(self, f): """Registers a function to run before each request.""" diff --git a/flask/blueprints.py b/flask/blueprints.py index 54fa33bc..d81d3c73 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -185,17 +185,30 @@ class Blueprint(_PackageBoundObject): return f return decorator - def app_template_filter(self, name = None): - """Like :meth:`Flask.template_filter` but for a blueprint. The filter - is available for the entire application. + def app_template_filter(self, name=None): + """Register a custom template filter, available application wide. Like + :meth:`Flask.template_filter` but for a blueprint. + + :param name: the optional name of the filter, otherwise the + function name will be used. """ def decorator(f): - def register_template(state): - state.app.jinja_env.filters[name or f.__name__] = f - self.record_once(register_template) + self.add_app_template_filter(f, name=name) return f return decorator + def add_app_template_filter(self, f, name=None): + """Register a custom template filter, available application wide. Like + :meth:`Flask.add_template_filter` but for a blueprint. Works exactly + like the :meth:`app_template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + def register_template(state): + state.app.jinja_env.filters[name or f.__name__] = f + self.record_once(register_template) + def before_request(self, f): """Like :meth:`Flask.before_request` but for a blueprint. This function is only executed before each request that is handled by a function of From 820d099e82aa8ee01e1e82b85e2c6636d63be98a Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 20:21:26 -0500 Subject: [PATCH 0042/2940] Add tests for template filter methods/decorators. --- flask/testsuite/blueprints.py | 109 ++++++++++++++++++++++++++++++++++ flask/testsuite/templating.py | 40 +++++++++++++ 2 files changed, 149 insertions(+) diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 3f65dd48..5bf81d92 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -504,6 +504,115 @@ class BlueprintTestCase(FlaskTestCase): rv = c.get('/py/bar/123') assert rv.status_code == 404 + def test_template_filter(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter() + def my_reverse(s): + return s[::-1] + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) + self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') + + def test_add_template_filter(self): + bp = flask.Blueprint('bp', __name__) + def my_reverse(s): + return s[::-1] + bp.add_app_template_filter(my_reverse) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) + self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') + + def test_template_filter_with_name(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter('strrev') + def my_reverse(s): + return s[::-1] + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) + self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') + + def test_add_template_filter_with_name(self): + bp = flask.Blueprint('bp', __name__) + def my_reverse(s): + return s[::-1] + bp.add_app_template_filter(my_reverse, 'strrev') + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) + self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') + + def test_template_filter_with_template(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + + def test_template_filter_after_route_with_template(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + app.register_blueprint(bp, url_prefix='/py') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + + def test_add_template_filter_with_template(self): + bp = flask.Blueprint('bp', __name__) + def super_reverse(s): + return s[::-1] + bp.add_app_template_filter(super_reverse) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + + def test_template_filter_with_name_and_template(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_filter('super_reverse') + def my_reverse(s): + return s[::-1] + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + + def test_add_template_filter_with_name_and_template(self): + bp = flask.Blueprint('bp', __name__) + def my_reverse(s): + return s[::-1] + bp.add_app_template_filter(my_reverse, 'super_reverse') + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 453bfb65..759fe0f3 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -93,6 +93,15 @@ class TemplatingTestCase(FlaskTestCase): self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') + def test_add_template_filter(self): + app = flask.Flask(__name__) + def my_reverse(s): + return s[::-1] + app.add_template_filter(my_reverse) + self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) + self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') + def test_template_filter_with_name(self): app = flask.Flask(__name__) @app.template_filter('strrev') @@ -102,6 +111,15 @@ class TemplatingTestCase(FlaskTestCase): self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') + def test_add_template_filter_with_name(self): + app = flask.Flask(__name__) + def my_reverse(s): + return s[::-1] + app.add_template_filter(my_reverse, 'strrev') + self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) + self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') + def test_template_filter_with_template(self): app = flask.Flask(__name__) @app.template_filter() @@ -113,6 +131,17 @@ class TemplatingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.data, 'dcba') + def test_add_template_filter_with_template(self): + app = flask.Flask(__name__) + def super_reverse(s): + return s[::-1] + app.add_template_filter(super_reverse) + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + def test_template_filter_with_name_and_template(self): app = flask.Flask(__name__) @app.template_filter('super_reverse') @@ -124,6 +153,17 @@ class TemplatingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.data, 'dcba') + def test_add_template_filter_with_name_and_template(self): + app = flask.Flask(__name__) + def my_reverse(s): + return s[::-1] + app.add_template_filter(my_reverse, 'super_reverse') + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'dcba') + def test_custom_template_loader(self): class MyFlask(flask.Flask): def create_global_jinja_loader(self): From 96f7beba46c52cfa4d9231d066271564f995e3c0 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 20:53:20 -0500 Subject: [PATCH 0043/2940] Document recent changes. --- CHANGES | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES b/CHANGES index 2fed4b4a..6f360814 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,18 @@ Relase date to be decided, codename to be chosen. - Session is now stored after callbacks so that if the session payload is stored in the session you can still modify it in an after request callback. +- The :class:`flask.Flask` class will avoid importing the provided import name + if it can (the required first parameter), to benefit tools which build Flask + instances programmatically. The Flask class will fall back to using import + on systems with custom module hooks, e.g. Google App Engine, or when the + import name is inside a zip archive (usually a .egg) prior to Python 2.7. +- Blueprints now have a decorator to add custom template filters application + wide, :meth:`flask.Blueprint.app_template_filter`. +- The Flask and Blueprint classes now have a non-decorator method for adding + custom template filters application wide, + :meth:`flask.Flask.add_template_filter` and + :meth:`flask.Blueprint.add_app_template_filter`. + Version 0.8.1 ------------- From e5161a773e2d562aed2df96a0c80b34004022cef Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sat, 25 Jun 2011 18:34:33 -0700 Subject: [PATCH 0044/2940] commas after introductory phrases --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index c4ded1fe..ef57e07c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ Welcome to Flask Welcome to Flask's documentation. This documentation is divided into different parts. I recommend that you get started with :ref:`installation` and then head over to the :ref:`quickstart`. -Besides the quickstart there is also a more detailed :ref:`tutorial` that +Besides the quickstart, there is also a more detailed :ref:`tutorial` that shows how to create a complete (albeit small) application with Flask. If you'd rather dive into the internals of Flask, check out the :ref:`api` documentation. Common patterns are described in the @@ -18,7 +18,7 @@ the :ref:`api` documentation. Common patterns are described in the Flask depends on two external libraries: the `Jinja2`_ template engine and the `Werkzeug`_ WSGI toolkit. These libraries are not documented -here. If you want to dive into their documentation check out the +here. If you want to dive into their documentation, check out the following links: - `Jinja2 Documentation `_ From 1a9e082a76df4b7433c0dda5d22e05b1e7c2c263 Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sat, 25 Jun 2011 19:14:34 -0700 Subject: [PATCH 0045/2940] tweaks to style --- docs/installation.rst | 110 +++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 55065b6d..d63a5842 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,38 +9,37 @@ Werkzeug is a toolkit for WSGI, the standard Python interface between web applications and a variety of servers for both development and deployment. Jinja2 renders templates. -So how do you get all that on your computer quickly? There are many ways, -as this section will explain, but the most kick-ass method is -virtualenv, so let's look at that first. +So how do you get all that on your computer quickly? There are many ways you +could do that, but the most kick-ass method is virtualenv, so let's have a look +at that first. -Either way, you will need Python 2.5 or higher to get started, so be sure -to have an up to date Python 2.x installation. At the time of writing, -the WSGI specification is not yet finalized for Python 3, so Flask cannot -support the 3.x series of Python. +You will need Python 2.5 or higher to get started, so be sure to have an +up-to-date Python 2.x installation. At the time of writing, the WSGI +specification has not yet been finalized for Python 3, so Flask cannot support +the 3.x series of Python. .. _virtualenv: virtualenv ---------- -Virtualenv is probably what you want to use during development, and in -production too if you have shell access there. +Virtualenv is probably what you want to use during development, and if you have +shell access to your production machines, you'll probably want to use it there, +too. -What problem does virtualenv solve? If you like Python as I do, -chances are you want to use it for other projects besides Flask-based -web applications. But the more projects you have, the more likely it is -that you will be working with different versions of Python itself, or at -least different versions of Python libraries. Let's face it; quite often -libraries break backwards compatibility, and it's unlikely that any serious -application will have zero dependencies. So what do you do if two or more -of your projects have conflicting dependencies? +What problem does virtualenv solve? If you like Python as much as I do, +chances are you want to use it for other projects besides Flask-based web +applications. But the more projects you have, the more likely it is that you +will be working with different versions of Python itself, or at least different +versions of Python libraries. Let's face it: quite often libraries break +backwards compatibility, and it's unlikely that any serious application will +have zero dependencies. So what do you do if two or more of your projects have +conflicting dependencies? -Virtualenv to the rescue! It basically enables multiple side-by-side -installations of Python, one for each project. It doesn't actually -install separate copies of Python, but it does provide a clever way -to keep different project environments isolated. - -So let's see how virtualenv works! +Virtualenv to the rescue! Virtualenv enables multiple side-by-side +installations of Python, one for each project. It doesn't actually install +separate copies of Python, but it does provide a clever way to keep different +project environments isolated. Let's see how virtualenv works. If you are on Mac OS X or Linux, chances are that one of the following two commands will work for you:: @@ -51,15 +50,15 @@ or even better:: $ sudo pip install virtualenv -One of these will probably install virtualenv on your system. Maybe it's -even in your package manager. If you use Ubuntu, try:: +One of these will probably install virtualenv on your system. Maybe it's even +in your package manager. If you use Ubuntu, try:: $ sudo apt-get install python-virtualenv If you are on Windows and don't have the `easy_install` command, you must install it first. Check the :ref:`windows-easy-install` section for more -information on how to do that. Once you have it installed, run the -same commands as above, but without the `sudo` prefix. +information about how to do that. Once you have it installed, run the same +commands as above, but without the `sudo` prefix. Once you have virtualenv installed, just fire up a shell and create your own environment. I usually create a project folder and an `env` @@ -71,28 +70,28 @@ folder within:: New python executable in env/bin/python Installing setuptools............done. -Now, whenever you want to work on a project, you only have to activate -the corresponding environment. On OS X and Linux, do the following:: +Now, whenever you want to work on a project, you only have to activate the +corresponding environment. On OS X and Linux, do the following:: $ . env/bin/activate -(Note the space between the dot and the script name. The dot means that -this script should run in the context of the current shell. If this command -does not work in your shell, try replacing the dot with ``source``.) +(Note the space between the dot and the script name. The dot means that this +script should run in the context of the current shell. If this command does +not work in your shell, try replacing the dot with ``source``) If you are a Windows user, the following command is for you:: $ env\scripts\activate -Either way, you should now be using your virtualenv (see how the prompt of +Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the virtualenv). -Now you can just enter the following command to get Flask activated in -your virtualenv:: +Now you can just enter the following command to get Flask activated in your +virtualenv:: $ easy_install Flask -A few seconds later you are good to go. +A few seconds later and you are good to go. System-Wide Installation @@ -103,15 +102,16 @@ This is possible as well, though I do not recommend it. Just run $ sudo easy_install Flask -(Run it in an Admin shell on Windows systems and without `sudo`.) +(On Windows systems, run it in a command-prompt window with administrator +privleges, and leave out `sudo`.) Living on the Edge ------------------ If you want to work with the latest version of Flask, there are two ways: you -can either let `easy_install` pull in the development version, or tell it -to operate on a git checkout. Either way, virtualenv is recommended. +can either let `easy_install` pull in the development version, or you can tell +it to operate on a git checkout. Either way, virtualenv is recommended. Get the git checkout in a new virtualenv and run in development mode:: @@ -127,8 +127,8 @@ Get the git checkout in a new virtualenv and run in development mode:: Finished processing dependencies for Flask This will pull in the dependencies and activate the git head as the current -version inside the virtualenv. Then you just have to ``git pull origin`` -to get the latest version. +version inside the virtualenv. Then all you have to do is run ``git pull +origin`` to update to the latest version. To just get the development version without git, do this instead:: @@ -147,29 +147,27 @@ To just get the development version without git, do this instead:: `easy_install` on Windows ------------------------- -On Windows, installation of `easy_install` is a little bit trickier because -slightly different rules apply on Windows than on Unix-like systems, but -it's not difficult. The easiest way to do it is to download the -`ez_setup.py`_ file and run it. The easiest way to run the file is to -open your downloads folder and double-click on the file. +On Windows, installation of `easy_install` is a little bit trickier, but still +quite easy. The easiest way to do it is to download the `ez_setup.py`_ file +and run it. The easiest way to run the file is to open your downloads folder +and double-click on the file. Next, add the `easy_install` command and other Python scripts to the command search path, by adding your Python installation's Scripts folder to the `PATH` environment variable. To do that, right-click on the -"Computer" icon on the Desktop or in the Start menu, and choose -"Properties". Then, on Windows Vista and Windows 7 click on "Advanced System -settings"; on Windows XP, click on the "Advanced" tab instead. Then click -on the "Environment variables" button and double-click on the "Path" -variable in the "System variables" section. There append the path of your -Python interpreter's Scripts folder; make sure you delimit it from -existing values with a semicolon. Assuming you are using Python 2.6 on +"Computer" icon on the Desktop or in the Start menu, and choose "Properties". +Then click on "Advanced System settings" (on Windows XP, click on the +"Advanced" tab instead). Then click on the "Environment variables" button and +double-click on the "Path" variable in the "System variables" section. There +append the path of your Python interpreter's Scripts folder. Be sure to delimit +it from existing values with a semicolon. Assuming you are using Python 2.6 on the default path, add the following value:: ;C:\Python26\Scripts -Then you are done. To check that it worked, open the Command Prompt and -execute ``easy_install``. If you have User Account Control enabled on -Windows Vista or Windows 7, it should prompt you for admin privileges. +And you are done! To check that it worked, open the Command Prompt and execute +``easy_install``. If you have User Account Control enabled on Windows Vista or +Windows 7, it should prompt you for administrator privileges. .. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py From c34a87033f19174029083fe928c9a65acbeb24d5 Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sat, 25 Jun 2011 19:16:44 -0700 Subject: [PATCH 0046/2940] tweaks to style and structure --- docs/installation.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index d63a5842..a7add061 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -156,12 +156,13 @@ Next, add the `easy_install` command and other Python scripts to the command search path, by adding your Python installation's Scripts folder to the `PATH` environment variable. To do that, right-click on the "Computer" icon on the Desktop or in the Start menu, and choose "Properties". -Then click on "Advanced System settings" (on Windows XP, click on the -"Advanced" tab instead). Then click on the "Environment variables" button and -double-click on the "Path" variable in the "System variables" section. There -append the path of your Python interpreter's Scripts folder. Be sure to delimit -it from existing values with a semicolon. Assuming you are using Python 2.6 on -the default path, add the following value:: +Then click on "Advanced System settings" (in Windows XP, click on the +"Advanced" tab instead). Then click on the "Environment variables" button. +Finally, double-click on the "Path" variable in the "System variables" section, +and add the path of your Python interpreter's Scripts folder. Be sure to +delimit it from existing values with a semicolon. Assuming you are using +Python 2.6 on the default path, add the following value:: + ;C:\Python26\Scripts From 593b003b5ca40e4a4a7d2a4319afaad91eac5c62 Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sat, 25 Jun 2011 19:17:37 -0700 Subject: [PATCH 0047/2940] tweak to one sentence --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index a7add061..f02b5cb1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -84,7 +84,7 @@ If you are a Windows user, the following command is for you:: $ env\scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of -your shell has changed to show the virtualenv). +your shell has changed to show the active environment). Now you can just enter the following command to get Flask activated in your virtualenv:: From df19ee95bda35825521ddaf210cbe17659d8f6bf Mon Sep 17 00:00:00 2001 From: Ori Livneh Date: Sat, 25 Jun 2011 19:55:01 -0700 Subject: [PATCH 0048/2940] more editorial work --- docs/quickstart.rst | 155 ++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index be3cecdd..2404361d 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,9 +3,9 @@ Quickstart ========== -Eager to get started? This page gives a good introduction on how to get -started with Flask. This assumes you already have Flask installed. If -you do not, head over to the :ref:`installation` section. +Eager to get started? This page gives a good introduction to Flask. It +assumes you already have Flask installed. If you do not, head over to the +:ref:`installation` section. A Minimal Application @@ -23,39 +23,39 @@ A minimal Flask application looks something like this:: if __name__ == '__main__': app.run() -Just save it as `hello.py` or something similar and run it with your -Python interpreter. Make sure to not call your application `flask.py` -because this would conflict with Flask itself. +Just save it as `hello.py` (or something similar) and run it with your Python +interpreter. Make sure to not call your application `flask.py` because this +would conflict with Flask itself. :: $ python hello.py * Running on http://127.0.0.1:5000/ -Head over to `http://127.0.0.1:5000/ `_, you should -see your hello world greeting. +Now head over to `http://127.0.0.1:5000/ `_, and you +should see your hello world greeting. So what did that code do? 1. First we imported the :class:`~flask.Flask` class. An instance of this class will be our WSGI application. The first argument is the name of - the application's module. If you are using a single module (as in this example) - you should use `__name__` because depending on whether it's started as - application or imported as module the name will be different - (``'__main__'`` versus the actual import name). For more information - on this technique, have a look at the :class:`~flask.Flask` documentation. -2. Next we create an instance of it. We pass it the name of the module / - package. This is needed so that Flask knows where it should look for - templates, static files and so on. -3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask - which URL should trigger our function. -4. The function then has a name which is also used to generate URLs to - that particular function, and returns the message we want to display in - the user's browser. -5. Finally we use the :meth:`~flask.Flask.run` function to run the - local server with our application. The ``if __name__ == '__main__':`` - makes sure the server only runs if the script is executed directly from - the Python interpreter and not used as imported module. + the application's module. If you are using a single module (as in this + example), you should use `__name__` because depending on if it's started as + application or imported as module the name will be different (``'__main__'`` + versus the actual import name). For more information, have a look at the + :class:`~flask.Flask` documentation. +2. Next we create an instance of this class. We pass it the name of the module + or package. This is needed so that Flask knows where to look for templates, + static files, and so on. +3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask what URL + should trigger our function. +4. The function is given a name which is also used to generate URLs for that + particular function, and returns the message we want to display in the + user's browser. +5. Finally we use the :meth:`~flask.Flask.run` function to run the local server + with our application. The ``if __name__ == '__main__':`` makes sure the + server only runs if the script is executed directly from the Python + interpreter and not used as imported module. To stop the server, hit control-C. @@ -74,7 +74,7 @@ To stop the server, hit control-C. app.run(host='0.0.0.0') - This tells your operating system to listen on a public IP. + This tells your operating system to listen on all public IPs. Debug Mode @@ -92,18 +92,18 @@ application object:: app.debug = True app.run() -Or pass it to `run`:: +Or pass it as a parameter to run:: app.run(debug=True) -Both will have exactly the same effect. +Both methods have the exact same effect. .. admonition:: Attention Even though the interactive debugger does not work in forking environments (which makes it nearly impossible to use on production servers), it still - allows the execution of arbitrary code. That makes it a major security - risk and therefore it **must never be used on production machines**. + allows the execution of arbitrary code. This makes it a major security risk + and therefore it **must never be used on production machines**. Screenshot of the debugger in action: @@ -114,21 +114,21 @@ Screenshot of the debugger in action: .. admonition:: Working With Other Debuggers - Debuggers interfere with each other. - That said, you may still wish to use the debugger in a tool of your choice. + Debuggers interfere with each other. + That said, you may still wish to use the debugger in a tool of your choice. Flask provides the following options to manage the debug process: * ``debug`` - whether to enable debug mode and catch exceptinos * ``use_debugger`` - whether to use the internal Flask debugger * ``use_reloader`` - whether to reload and fork the process on exception - ``debug`` must be True (i.e., exceptions must caught) in order for the + ``debug`` must be True (i.e., exceptions must caught) in order for the other two options to have any value. - If you're using Aptana/Eclipse for debugging you'll need to set both + If you're using Aptana/Eclipse for debugging you'll need to set both ``use_debugger`` and ``use_reloader`` to False. - A possible useful pattern for configuration is to set the following in your + A possible useful pattern for configuration is to set the following in your config.yaml (change the block as approriate for your application, of course):: FLASK: @@ -159,8 +159,8 @@ mobile devices with slower network connections. If the user can directly go to the desired page without having to hit the index page it is more likely they will like the page and come back next time. -As you have seen above, the :meth:`~flask.Flask.route` decorator is used -to bind a function to a URL. Here are some basic examples:: +As you have seen above, the :meth:`~flask.Flask.route` decorator is used to +bind a function to a URL. Here are some basic examples:: @app.route('/') def index(): @@ -170,16 +170,16 @@ to bind a function to a URL. Here are some basic examples:: def hello(): return 'Hello World' -But there is more to it! You can make certain parts of the URL dynamic -and attach multiple rules to a function. +But there is more to it! You can make certain parts of the URL dynamic and +attach multiple rules to a function. Variable Rules `````````````` To add variable parts to a URL you can mark these special sections as -````. Such a part is then passed as keyword argument to -your function. Optionally a converter can be specified by specifying a -rule with ````. Here are some nice examples:: +````. Such a part is then passed as keyword argument to your +function. Optionally a converter can be specified by specifying a rule with +````. Here are some nice examples:: @app.route('/user/') def show_user_profile(username): @@ -201,9 +201,9 @@ The following converters exist: .. admonition:: Unique URLs / Redirection Behaviour - Flask's URL rules are based on Werkzeug's routing module. The idea - behind that module is to ensure nice-looking and also unique URLs based - on behaviour coined by Apache and earlier servers. + Flask's URL rules are based on Werkzeug's routing module. The idea behind + that module is to ensure beautiful and unique also unique URLs based on + precedents laid down by Apache and earlier HTTP servers. Take these two rules:: @@ -215,21 +215,20 @@ The following converters exist: def about(): return 'The about page' - They look rather similar, the difference is the trailing slash in the - URL *definition*. In the first case, the canonical URL for the - `projects` endpoint has a trailing slash. It's similar to a folder in - that sense. Accessing it without a trailing slash will cause Flask to - redirect to the canonical URL with the trailing slash. + Though they look rather similar, they differ in their use of the trailing + slash in the URL *definition*. In the first case, the canonical URL for the + `projects` endpoint has a trailing slash. In that sense, it is similar to + a folder on a file system. Accessing it without a trailing slash will cause + Flask to redirect to the canonical URL with the trailing slash. - However, in the second case the URL is defined without a slash so it - behaves similar to a file and accessing the URL with a trailing slash - will result in a 404 error. + In the second case, however, the URL is defined without a trailing slash, + rather like the pathname of a file on UNIX-like systems. Accessing the URL + with a trailing slash will produce a 404 "Not Found" error. - Why is this? This allows relative URLs to continue working if users - access the page when they forget a trailing slash. This behaviour is - also consistent with how Apache and other servers work. Also, the URLs - will stay unique which ensures search engines do not index the same page - twice. + This behavior allows relative URLs to continue working if users access the + page when they forget a trailing slash, consistent with how with how Apache + and other servers work. Also, the URLs will stay unique, which helps search + engines avoid indexing the same page twice. .. _url-building: @@ -237,12 +236,12 @@ The following converters exist: URL Building ```````````` -If it can match URLs, can it also generate them? Of course it can. To +If it can match URLs, can Flask also generate them? Of course it can. To build a URL to a specific function you can use the :func:`~flask.url_for` -function. It accepts the name of the function as first argument and a -number of keyword arguments, each corresponding to the variable part of -the URL rule. Unknown variable parts are appended to the URL as query -parameter. Here are some examples: +function. It accepts the name of the function as first argument and a number +of keyword arguments, each corresponding to the variable part of the URL rule. +Unknown variable parts are appended to the URL as query parameters. Here are +some examples: >>> from flask import Flask, url_for >>> app = Flask(__name__) @@ -266,30 +265,30 @@ parameter. Here are some examples: /login?next=/ /user/John%20Doe -(This also uses the :meth:`~flask.Flask.test_request_context` method -explained below. It basically tells Flask to think we are handling a -request even though we are not, we are in an interactive Python shell. -Have a look at the explanation below: :ref:`context-locals`.) +(This also uses the :meth:`~flask.Flask.test_request_context` method, explained +below. It tells Flask to behave as though it is handling a request, even +though were are interacting with it through a Python shell. Have a look at the +explanation below. :ref:`context-locals`). -Why would you want to build URLs instead of hardcoding them in your +Why would you want to build URLs instead of hard-coding them into your templates? There are three good reasons for this: -1. reversing is often more descriptive than hardcoding the URLs. Also and - more importantly you can change URLs in one go without having to change - the URLs all over the place. +1. Reversing is often more descriptive than hard-coding the URLs. More + importantly, it allows you to change URLs in one go, without having to + remember to change URLs all over the place. 2. URL building will handle escaping of special characters and Unicode - data transparently for you, you don't have to deal with that. -3. If your application is placed outside the URL root (so say in - ``/myapplication`` instead of ``/``), :func:`~flask.url_for` will - handle that properly for you. + data transparently for you, so you don't have to deal with them. +3. If your application is placed outside the URL root (say, in + ``/myapplication`` instead of ``/``), :func:`~flask.url_for` will handle + that properly for you. HTTP Methods ```````````` -HTTP (the protocol web applications are speaking) knows different methods -to access URLs. By default a route only answers to `GET` requests, but -that can be changed by providing the `methods` argument to the +HTTP (the protocol web applications are speaking) knows different methods for +accessing URLs. By default, a route only answers to `GET` requests, but that +can be changed by providing the `methods` argument to the :meth:`~flask.Flask.route` decorator. Here are some examples:: @app.route('/login', methods=['GET', 'POST']) From ed40eacf423b9a182f3ca75287ddba9a5eb77801 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 21:45:19 -0500 Subject: [PATCH 0049/2940] Add tiny doc touchups. --- docs/installation.rst | 2 +- docs/quickstart.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index f02b5cb1..46f43186 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -77,7 +77,7 @@ corresponding environment. On OS X and Linux, do the following:: (Note the space between the dot and the script name. The dot means that this script should run in the context of the current shell. If this command does -not work in your shell, try replacing the dot with ``source``) +not work in your shell, try replacing the dot with ``source``.) If you are a Windows user, the following command is for you:: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 2404361d..368bd96c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -122,7 +122,7 @@ Screenshot of the debugger in action: * ``use_debugger`` - whether to use the internal Flask debugger * ``use_reloader`` - whether to reload and fork the process on exception - ``debug`` must be True (i.e., exceptions must caught) in order for the + ``debug`` must be True (i.e., exceptions must be caught) in order for the other two options to have any value. If you're using Aptana/Eclipse for debugging you'll need to set both From b9907b496911d0d8676225151a36c28fe4f4b72f Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 22:22:04 -0500 Subject: [PATCH 0050/2940] Expand get_flashed_messages tests. Ready to pull request #336. http://github.com/mitsuhiko/flask/pull/336 --- flask/testsuite/basic.py | 49 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index a11f7806..5a41d03e 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -321,22 +321,61 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/test') def test(): + messages = flask.get_flashed_messages() + self.assert_equal(len(messages), 3) + self.assert_equal(messages[0], u'Hello World') + self.assert_equal(messages[1], u'Hello World') + self.assert_equal(messages[2], flask.Markup(u'Testing')) + return '' + + @app.route('/test_with_categories') + def test_with_categories(): messages = flask.get_flashed_messages(with_categories=True) self.assert_equal(len(messages), 3) self.assert_equal(messages[0], ('message', u'Hello World')) self.assert_equal(messages[1], ('error', u'Hello World')) self.assert_equal(messages[2], ('warning', flask.Markup(u'Testing'))) return '' - messages = flask.get_flashed_messages() - self.assert_equal(len(messages), 3) + + @app.route('/test_filter') + def test_filter(): + messages = flask.get_flashed_messages(category_filter=['message'], with_categories=True) + self.assert_equal(len(messages), 1) + self.assert_equal(messages[0], ('message', u'Hello World')) + return '' + + @app.route('/test_filters') + def test_filters(): + messages = flask.get_flashed_messages(category_filter=['message', 'warning'], with_categories=True) + self.assert_equal(len(messages), 2) + self.assert_equal(messages[0], ('message', u'Hello World')) + self.assert_equal(messages[1], ('warning', flask.Markup(u'Testing'))) + return '' + + @app.route('/test_filters_without_returning_categories') + def test_filters(): + messages = flask.get_flashed_messages(category_filter=['message', 'warning']) + self.assert_equal(len(messages), 2) self.assert_equal(messages[0], u'Hello World') - self.assert_equal(messages[1], u'Hello World') - self.assert_equal(messages[2], flask.Markup(u'Testing')) + self.assert_equal(messages[1], flask.Markup(u'Testing')) + return '' c = app.test_client() - c.get('/') + c.get('/') # Flash some messages. c.get('/test') + c.get('/') # Flash more messages. + c.get('/test_with_categories') + + c.get('/') # Flash more messages. + c.get('/test_filter') + + c.get('/') # Flash more messages. + c.get('/test_filters') + + c.get('/') # Flash more messages. + c.get('/test_filters_without_returning_categories') + def test_request_processing(self): app = flask.Flask(__name__) evts = [] From 676b3a4c13986f5743b8e6f3fa4d7c6cc2a401a4 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 22:44:06 -0500 Subject: [PATCH 0051/2940] Check status code in test client or fail silently. --- flask/testsuite/basic.py | 42 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 5a41d03e..f543ba9f 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -319,7 +319,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.flash(flask.Markup(u'Testing'), 'warning') return '' - @app.route('/test') + @app.route('/test/') def test(): messages = flask.get_flashed_messages() self.assert_equal(len(messages), 3) @@ -328,7 +328,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(messages[2], flask.Markup(u'Testing')) return '' - @app.route('/test_with_categories') + @app.route('/test_with_categories/') def test_with_categories(): messages = flask.get_flashed_messages(with_categories=True) self.assert_equal(len(messages), 3) @@ -337,14 +337,14 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(messages[2], ('warning', flask.Markup(u'Testing'))) return '' - @app.route('/test_filter') + @app.route('/test_filter/') def test_filter(): messages = flask.get_flashed_messages(category_filter=['message'], with_categories=True) self.assert_equal(len(messages), 1) self.assert_equal(messages[0], ('message', u'Hello World')) return '' - @app.route('/test_filters') + @app.route('/test_filters/') def test_filters(): messages = flask.get_flashed_messages(category_filter=['message', 'warning'], with_categories=True) self.assert_equal(len(messages), 2) @@ -352,7 +352,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(messages[1], ('warning', flask.Markup(u'Testing'))) return '' - @app.route('/test_filters_without_returning_categories') + @app.route('/test_filters_without_returning_categories/') def test_filters(): messages = flask.get_flashed_messages(category_filter=['message', 'warning']) self.assert_equal(len(messages), 2) @@ -360,21 +360,33 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(messages[1], flask.Markup(u'Testing')) return '' + # Note: if status code assertions are missing, failed tests still pass. + # + # Since app.test_client() does not set debug=True, the AssertionErrors + # in the view functions are swallowed and the only indicator is a 500 + # status code. + # + # Also, create new test client on each test to clean flashed messages. + c = app.test_client() - c.get('/') # Flash some messages. - c.get('/test') + c.get('/') + assert c.get('/test/').status_code == 200 - c.get('/') # Flash more messages. - c.get('/test_with_categories') + c = app.test_client() + c.get('/') + assert c.get('/test_with_categories/').status_code == 200 - c.get('/') # Flash more messages. - c.get('/test_filter') + c = app.test_client() + c.get('/') + assert c.get('/test_filter/').status_code == 200 - c.get('/') # Flash more messages. - c.get('/test_filters') + c = app.test_client() + c.get('/') + assert c.get('/test_filters/').status_code == 200 - c.get('/') # Flash more messages. - c.get('/test_filters_without_returning_categories') + c = app.test_client() + c.get('/') + assert c.get('/test_filters_without_returning_categories/').status_code == 200 def test_request_processing(self): app = flask.Flask(__name__) From fa069f94dec3aeec4a81a00e7bbd8d95e400bf5f Mon Sep 17 00:00:00 2001 From: Steven Osborn Date: Sun, 16 Oct 2011 19:44:37 -0700 Subject: [PATCH 0052/2940] Allow category filtering in get_flashed_messages to allow rending categories in separate html blocks --- docs/patterns/flashing.rst | 23 +++++++++++++++++++++++ flask/helpers.py | 8 +++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index 7abe7165..be8fcfe8 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -117,3 +117,26 @@ categories. The loop looks slightly different in that situation then: This is just one example of how to render these flashed messages. One might also use the category to add a prefix such as ``Error:`` to the message. + +Filtering Flash Messages +------------------------ + +.. versionadded:: 0.9 + +Optionally you can pass a list of categories which filters the results of +:func:`~flask.get_flashed_messages`. This is useful if you wish to +render each category in a separate block. + +.. sourcecode:: html+jinja + +{% with errors = get_flashed_messages(category_filter=["error"]) %} + {% if errors %} +

+
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+ {% endif %} +{% endwith %} \ No newline at end of file diff --git a/flask/helpers.py b/flask/helpers.py index 3c9a5669..aa813003 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -264,7 +264,7 @@ def flash(message, category='message'): session.setdefault('_flashes', []).append((category, message)) -def get_flashed_messages(with_categories=False): +def get_flashed_messages(with_categories=False, category_filter=[]): """Pulls all flashed messages from the session and returns them. Further calls in the same request to the function will return the same messages. By default just the messages are returned, @@ -282,12 +282,18 @@ def get_flashed_messages(with_categories=False): .. versionchanged:: 0.3 `with_categories` parameter added. + .. versionchanged: 0.9 + `category_filter` parameter added. + :param with_categories: set to `True` to also receive categories. + :param category_filter: whitelist of categories to limit return values """ flashes = _request_ctx_stack.top.flashes if flashes is None: _request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \ if '_flashes' in session else [] + if category_filter: + flashes = filter(lambda f: f[0] in category_filter, flashes) if not with_categories: return [x[1] for x in flashes] return flashes From 81010bf7afb786d3a19ddc2469f9bfdcc9e0d194 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 23:10:21 -0500 Subject: [PATCH 0053/2940] Add get_flashed_messages to CHANGES, expand docs. --- CHANGES | 4 +++- flask/helpers.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 6f360814..9a712afc 100644 --- a/CHANGES +++ b/CHANGES @@ -34,7 +34,9 @@ Relase date to be decided, codename to be chosen. custom template filters application wide, :meth:`flask.Flask.add_template_filter` and :meth:`flask.Blueprint.add_app_template_filter`. - +- The :func:`flask.get_flashed_messages` function now allows rendering flashed + message categories in separate blocks, through a ``category_filter`` + argument. Version 0.8.1 ------------- diff --git a/flask/helpers.py b/flask/helpers.py index aa813003..56c59199 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -251,7 +251,7 @@ def flash(message, category='message'): flashed message from the session and to display it to the user, the template has to call :func:`get_flashed_messages`. - .. versionchanged: 0.3 + .. versionchanged:: 0.3 `category` parameter added. :param message: the message to be flashed. @@ -271,6 +271,16 @@ def get_flashed_messages(with_categories=False, category_filter=[]): but when `with_categories` is set to `True`, the return value will be a list of tuples in the form ``(category, message)`` instead. + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (`True` gives a tuple, where `False` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + Example usage: .. sourcecode:: html+jinja @@ -279,10 +289,27 @@ def get_flashed_messages(with_categories=False, category_filter=[]):

{{ msg }} {% endfor %} + Example usage similar to http://twitter.github.com/bootstrap/#alerts: + + .. sourcecode:: html+jinja + + {% with errors = get_flashed_messages(category_filter=["error"]) %} + {% if errors %} +

+ × +
    + {%- for msg in errors %} +
  • {{ msg }}
  • + {% endfor -%} +
+
+ {% endif %} + {% endwith %} + .. versionchanged:: 0.3 `with_categories` parameter added. - .. versionchanged: 0.9 + .. versionchanged:: 0.9 `category_filter` parameter added. :param with_categories: set to `True` to also receive categories. From c93ea5551c6928745414f186ef465e2699a6833b Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 23:16:43 -0500 Subject: [PATCH 0054/2940] Keep flashed message examples in one place. --- docs/patterns/flashing.rst | 23 ++++++++++++----------- flask/helpers.py | 25 +------------------------ 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index be8fcfe8..f3d80d32 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -129,14 +129,15 @@ render each category in a separate block. .. sourcecode:: html+jinja -{% with errors = get_flashed_messages(category_filter=["error"]) %} - {% if errors %} -
-
    - {% for message in messages %} -
  • {{ message }}
  • - {% endfor %} -
-
- {% endif %} -{% endwith %} \ No newline at end of file + {% with errors = get_flashed_messages(category_filter=["error"]) %} + {% if errors %} +
+ × +
    + {%- for msg in errors %} +
  • {{ msg }}
  • + {% endfor -%} +
+
+ {% endif %} + {% endwith %} diff --git a/flask/helpers.py b/flask/helpers.py index 56c59199..25250d26 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -281,30 +281,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]): * `category_filter` filters the messages down to only those matching the provided categories. - Example usage: - - .. sourcecode:: html+jinja - - {% for category, msg in get_flashed_messages(with_categories=true) %} -

{{ msg }} - {% endfor %} - - Example usage similar to http://twitter.github.com/bootstrap/#alerts: - - .. sourcecode:: html+jinja - - {% with errors = get_flashed_messages(category_filter=["error"]) %} - {% if errors %} -

- × -
    - {%- for msg in errors %} -
  • {{ msg }}
  • - {% endfor -%} -
-
- {% endif %} - {% endwith %} + See :ref:`message-flashing-pattern` for examples. .. versionchanged:: 0.3 `with_categories` parameter added. From 46e7bc70833e803657726aaff7c63ca4f4865c79 Mon Sep 17 00:00:00 2001 From: Joe Esposito Date: Mon, 24 Oct 2011 17:46:35 -0400 Subject: [PATCH 0055/2940] In Flask.run, now when the host/port argument is None, it will use its default value. --- flask/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flask/app.py b/flask/app.py index 0e462020..b70162c1 100644 --- a/flask/app.py +++ b/flask/app.py @@ -695,6 +695,10 @@ class Flask(_PackageBoundObject): information. """ from werkzeug.serving import run_simple + if host is None: + host = '127.0.0.1' + if port is None: + port = 5000 if debug is not None: self.debug = bool(debug) options.setdefault('use_reloader', self.debug) From 19d32cb17285e2614d8aecbf4b0f63cfdac629cf Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 23:33:13 -0500 Subject: [PATCH 0056/2940] Update Flask.run signature, note defaults. --- flask/app.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/flask/app.py b/flask/app.py index b70162c1..26519d40 100644 --- a/flask/app.py +++ b/flask/app.py @@ -664,7 +664,7 @@ class Flask(_PackageBoundObject): # existing views. context.update(orig_ctx) - def run(self, host='127.0.0.1', port=5000, debug=None, **options): + def run(self, host=None, port=None, debug=None, **options): """Runs the application on a local development server. If the :attr:`debug` flag is set the server will automatically reload for code changes and show a debugger in case an exception happened. @@ -684,9 +684,10 @@ class Flask(_PackageBoundObject): won't catch any exceptions because there won't be any to catch. - :param host: the hostname to listen on. set this to ``'0.0.0.0'`` - to have the server available externally as well. - :param port: the port of the webserver + :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to + have the server available externally as well. Defaults to + ``'127.0.0.1'``. + :param port: the port of the webserver. Defaults to ``5000``. :param debug: if given, enable or disable debug mode. See :attr:`debug`. :param options: the options to be forwarded to the underlying From 234ac198cb9d3955d19d3ebe74cbbd91a2d4bb92 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Mon, 16 Jan 2012 23:38:36 -0500 Subject: [PATCH 0057/2940] Add updated Flask.run to CHANGES. --- CHANGES | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 9a712afc..970f183a 100644 --- a/CHANGES +++ b/CHANGES @@ -37,6 +37,12 @@ Relase date to be decided, codename to be chosen. - The :func:`flask.get_flashed_messages` function now allows rendering flashed message categories in separate blocks, through a ``category_filter`` argument. +- The :meth:`flask.Flask.run` method now accepts `None` for `host` and `port` + arguments, using default values when `None`. This allows for calling run + using configuration values, e.g. ``app.run(app.config.get('MYHOST'), + app.config.get('MYPORT'))``, with proper behavior whether or not a config + file is provided. + Version 0.8.1 ------------- From 49b77fbc7aa826efbf0b58d6973ef02c50e8fc66 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 17 Jan 2012 11:38:00 -0500 Subject: [PATCH 0058/2940] Add missing colons to versionadded. --- flask/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 26519d40..ccce7046 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1120,7 +1120,7 @@ class Flask(_PackageBoundObject): registered error handlers and fall back to returning the exception as response. - .. versionadded: 0.3 + .. versionadded:: 0.3 """ handlers = self.error_handler_spec.get(request.blueprint) if handlers and e.code in handlers: @@ -1189,7 +1189,7 @@ class Flask(_PackageBoundObject): for a 500 internal server error is used. If no such handler exists, a default 500 internal server error message is displayed. - .. versionadded: 0.3 + .. versionadded:: 0.3 """ exc_type, exc_value, tb = sys.exc_info() From 2e5de9829774ca10682b298544bf3ce2bf61bab7 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 17 Jan 2012 19:33:48 -0500 Subject: [PATCH 0059/2940] Use app.testing=True for asserts in messages test. --- flask/testsuite/basic.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index f543ba9f..e6a278e5 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -309,8 +309,15 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): + # Be sure app.testing=True below, else tests can fail silently. + # + # Specifically, if app.testing is not set to True, the AssertionErrors + # in the view functions will cause a 500 response to the test client + # instead of propagating exceptions. + app = flask.Flask(__name__) app.secret_key = 'testkey' + app.testing = True @app.route('/') def index(): @@ -360,33 +367,27 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(messages[1], flask.Markup(u'Testing')) return '' - # Note: if status code assertions are missing, failed tests still pass. - # - # Since app.test_client() does not set debug=True, the AssertionErrors - # in the view functions are swallowed and the only indicator is a 500 - # status code. - # - # Also, create new test client on each test to clean flashed messages. + # Create new test client on each test to clean flashed messages. c = app.test_client() c.get('/') - assert c.get('/test/').status_code == 200 + c.get('/test/') c = app.test_client() c.get('/') - assert c.get('/test_with_categories/').status_code == 200 + c.get('/test_with_categories/') c = app.test_client() c.get('/') - assert c.get('/test_filter/').status_code == 200 + c.get('/test_filter/') c = app.test_client() c.get('/') - assert c.get('/test_filters/').status_code == 200 + c.get('/test_filters/') c = app.test_client() c.get('/') - assert c.get('/test_filters_without_returning_categories/').status_code == 200 + c.get('/test_filters_without_returning_categories/') def test_request_processing(self): app = flask.Flask(__name__) From 56177bcbd16f9e73252d7af7c78d25dc60ed946d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 17 Jan 2012 19:43:11 -0500 Subject: [PATCH 0060/2940] Document app.testing=True for test client, #381. --- flask/app.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/flask/app.py b/flask/app.py index ccce7046..15e432de 100644 --- a/flask/app.py +++ b/flask/app.py @@ -716,6 +716,17 @@ class Flask(_PackageBoundObject): """Creates a test client for this application. For information about unit testing head over to :ref:`testing`. + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + The test client can be used in a `with` block to defer the closing down of the context until the end of the `with` block. This is useful if you want to access the context locals for testing:: From b786eac5574e478d51314333fb6309456bff7b76 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 18 Jan 2012 18:57:05 -0500 Subject: [PATCH 0061/2940] Add test for limited imp loaders, #380. --- flask/testsuite/config.py | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index e0f7005f..00f77cea 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -8,10 +8,14 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement + import os import sys import flask +import pkgutil import unittest +from contextlib import contextmanager from flask.testsuite import FlaskTestCase @@ -84,6 +88,35 @@ class ConfigTestCase(FlaskTestCase): self.assert_equal(app.permanent_session_lifetime.seconds, 42) +class LimitedLoaderMockWrapper(object): + def __init__(self, loader): + self.loader = loader + + def __getattr__(self, name): + if name in ('archive', 'get_filename'): + msg = 'Mocking a loader which does not have `%s.`' % name + raise AttributeError, msg + return getattr(self.loader, name) + + +@contextmanager +def patch_pkgutil_get_loader(wrapper_class=LimitedLoaderMockWrapper): + """Patch pkgutil.get_loader to give loader without get_filename or archive. + + This provides for tests where a system has custom loaders, e.g. Google App + Engine's HardenedModulesHook, which have neither the `get_filename` method + nor the `archive` attribute. + """ + old_get_loader = pkgutil.get_loader + def get_loader(*args, **kwargs): + return wrapper_class(old_get_loader(*args, **kwargs)) + try: + pkgutil.get_loader = get_loader + yield + finally: + pkgutil.get_loader = old_get_loader + + class InstanceTestCase(FlaskTestCase): def test_explicit_instance_paths(self): @@ -133,6 +166,24 @@ class InstanceTestCase(FlaskTestCase): if 'site_app' in sys.modules: del sys.modules['site_app'] + def test_installed_module_paths_with_limited_loader(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + sys.path.append(site_packages) + with patch_pkgutil_get_loader(): + try: + import site_app + self.assert_equal(site_app.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_app-instance')) + finally: + sys.prefix = real_prefix + sys.path.remove(site_packages) + if 'site_app' in sys.modules: + del sys.modules['site_app'] + def test_installed_package_paths(self): here = os.path.abspath(os.path.dirname(__file__)) expected_prefix = os.path.join(here, 'test_apps') @@ -150,6 +201,24 @@ class InstanceTestCase(FlaskTestCase): if 'installed_package' in sys.modules: del sys.modules['installed_package'] + def test_installed_package_paths_with_limited_loader(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + installed_path = os.path.join(expected_prefix, 'path') + sys.path.append(installed_path) + with patch_pkgutil_get_loader(): + try: + import installed_package + self.assert_equal(installed_package.app.instance_path, + os.path.join(expected_prefix, 'var', + 'installed_package-instance')) + finally: + sys.prefix = real_prefix + sys.path.remove(installed_path) + if 'installed_package' in sys.modules: + del sys.modules['installed_package'] + def test_prefix_package_paths(self): here = os.path.abspath(os.path.dirname(__file__)) expected_prefix = os.path.join(here, 'test_apps') @@ -167,6 +236,24 @@ class InstanceTestCase(FlaskTestCase): if 'site_package' in sys.modules: del sys.modules['site_package'] + def test_prefix_package_paths_with_limited_loader(self): + here = os.path.abspath(os.path.dirname(__file__)) + expected_prefix = os.path.join(here, 'test_apps') + real_prefix, sys.prefix = sys.prefix, expected_prefix + site_packages = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages') + sys.path.append(site_packages) + with patch_pkgutil_get_loader(): + try: + import site_package + self.assert_equal(site_package.app.instance_path, + os.path.join(expected_prefix, 'var', + 'site_package-instance')) + finally: + sys.prefix = real_prefix + sys.path.remove(site_packages) + if 'site_package' in sys.modules: + del sys.modules['site_package'] + def test_egg_installed_paths(self): here = os.path.abspath(os.path.dirname(__file__)) expected_prefix = os.path.join(here, 'test_apps') From 76c1a1f7227ca13f3c5952b248af93b75e9a95c9 Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 23 Jan 2012 20:12:56 +0100 Subject: [PATCH 0062/2940] fixed spelling of "instantiate" while the interwebs suggest "instanciate" might be a valid spelling, it seems quite uncommon and potentially irritating (to pedants like myself) --- docs/patterns/appdispatch.rst | 2 +- docs/views.rst | 2 +- flask/views.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 93b4af96..177ade2b 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -58,7 +58,7 @@ Dispatch by Subdomain Sometimes you might want to use multiple instances of the same application with different configurations. Assuming the application is created inside -a function and you can call that function to instanciate it, that is +a function and you can call that function to instantiate it, that is really easy to implement. In order to develop your application to support creating new instances in functions have a look at the :ref:`app-factories` pattern. diff --git a/docs/views.rst b/docs/views.rst index feee0a8b..9270921b 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -74,7 +74,7 @@ enough to explain the basic principle. When you have a class based view the question comes up what `self` points to. The way this works is that whenever the request is dispatched a new instance of the class is created and the :meth:`~flask.views.View.dispatch_request` method is called with -the parameters from the URL rule. The class itself is instanciated with +the parameters from the URL rule. The class itself is instantiated with the parameters passed to the :meth:`~flask.views.View.as_view` function. For instance you can write a class like this:: diff --git a/flask/views.py b/flask/views.py index 3e35e46f..811fa196 100644 --- a/flask/views.py +++ b/flask/views.py @@ -72,7 +72,7 @@ class View(object): def as_view(cls, name, *class_args, **class_kwargs): """Converts the class into an actual view function that can be used with the routing system. What it does internally is generating - a function on the fly that will instanciate the :class:`View` + a function on the fly that will instantiate the :class:`View` on each request and call the :meth:`dispatch_request` method on it. The arguments passed to :meth:`as_view` are forwarded to the @@ -90,7 +90,7 @@ class View(object): # we attach the view class to the view function for two reasons: # first of all it allows us to easily figure out what class based - # view this thing came from, secondly it's also used for instanciating + # view this thing came from, secondly it's also used for instantiating # the view class so you can actually replace it with something else # for testing purposes and debugging. view.view_class = cls From 2792dcf23e848472767a075e7c30e28890fd539d Mon Sep 17 00:00:00 2001 From: FND Date: Mon, 23 Jan 2012 20:27:42 +0100 Subject: [PATCH 0063/2940] simplified as_view documentation in the process, rewrapped lines to 78 chars (the file's current maximum) --- flask/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask/views.py b/flask/views.py index 811fa196..150e84c3 100644 --- a/flask/views.py +++ b/flask/views.py @@ -70,10 +70,10 @@ class View(object): @classmethod def as_view(cls, name, *class_args, **class_kwargs): - """Converts the class into an actual view function that can be - used with the routing system. What it does internally is generating - a function on the fly that will instantiate the :class:`View` - on each request and call the :meth:`dispatch_request` method on it. + """Converts the class into an actual view function that can be used + with the routing system. Internally this generates a function on the + fly which will instantiate the :class:`View` on each request and call + the :meth:`dispatch_request` method on it. The arguments passed to :meth:`as_view` are forwarded to the constructor of the class. From c5ebf9a97d0443bff59ff6a37aee8afd870ceabb Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 24 Jan 2012 16:48:04 -0500 Subject: [PATCH 0064/2940] Added PATCH method to the list of HTTP method functions for use in the flask.views.MethodView class. --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 150e84c3..2aaaf156 100644 --- a/flask/views.py +++ b/flask/views.py @@ -12,7 +12,7 @@ from .globals import request http_method_funcs = frozenset(['get', 'post', 'head', 'options', - 'delete', 'put', 'trace']) + 'delete', 'put', 'trace', 'patch']) class View(object): From 92dbe3153a9e0c007ecd1987ccd5f3d796c901af Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 24 Jan 2012 18:00:14 -0500 Subject: [PATCH 0065/2940] Export .epub with docs, #388. --- Makefile | 3 ++- docs/_templates/sidebarintro.html | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 43f47275..b7094ce6 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,13 @@ clean-pyc: find . -name '*~' -exec rm -f {} + upload-docs: - $(MAKE) -C docs html dirhtml latex + $(MAKE) -C docs html dirhtml latex epub $(MAKE) -C docs/_build/latex all-pdf cd docs/_build/; mv html flask-docs; zip -r flask-docs.zip flask-docs; mv flask-docs html rsync -a docs/_build/dirhtml/ pocoo.org:/var/www/flask.pocoo.org/docs/ rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip + rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub docs: $(MAKE) -C docs html diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 850fe86a..76777225 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -9,6 +9,7 @@

Useful Links

From 36f3184396f72b106911f497ce43c3415cb4f551 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 26 Jan 2012 13:24:15 -0500 Subject: [PATCH 0066/2940] Automate .mobi generation of docs, #388. --- Makefile | 6 ++++++ docs/_templates/sidebarintro.html | 1 + 2 files changed, 7 insertions(+) diff --git a/Makefile b/Makefile index b7094ce6..6fa48693 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ clean-pyc: find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + +# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html upload-docs: $(MAKE) -C docs html dirhtml latex epub $(MAKE) -C docs/_build/latex all-pdf @@ -30,6 +31,11 @@ upload-docs: rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub + @echo 'Building .mobi from .epub...' + @echo 'Command `ebook-covert` is provided by calibre package.' + @echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).' + @echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.' + ssh -X pocoo.org ebook-convert /var/www/flask.pocoo.org/docs/flask-docs.epub /var/www/flask.pocoo.org/docs/flask-docs.mobi --cover http://flask.pocoo.org/docs/_images/logo-full.png --authors 'Armin Ronacher' docs: $(MAKE) -C docs html diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 76777225..26e32261 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -10,6 +10,7 @@

Useful Links

From 5cb50a46eeb29dddacf21d0348050e02ff96c3b5 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 31 Jan 2012 05:46:18 -0500 Subject: [PATCH 0067/2940] Fix Message Flashing doc, from SwashBuckla #pocoo. Provide a full example as promised in the doc. --- docs/patterns/flashing.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/patterns/flashing.rst b/docs/patterns/flashing.rst index f3d80d32..5f3b02eb 100644 --- a/docs/patterns/flashing.rst +++ b/docs/patterns/flashing.rst @@ -16,7 +16,11 @@ Simple Flashing So here is a full example:: - from flask import flash, redirect, url_for, render_template + from flask import Flask, flash, redirect, render_template, \ + request, url_for + + app = Flask(__name__) + app.secret_key = 'some_secret' @app.route('/') def index(): @@ -27,13 +31,17 @@ So here is a full example:: error = None if request.method == 'POST': if request.form['username'] != 'admin' or \ - request.form['password'] != 'secret': + request.form['password'] != 'secret': error = 'Invalid credentials' else: flash('You were successfully logged in') return redirect(url_for('index')) return render_template('login.html', error=error) + if __name__ == "__main__": + app.run() + + And here the ``layout.html`` template which does the magic: .. sourcecode:: html+jinja From 4aebc267bc67c5d8a1687c0e5a7ecc949d6e7d20 Mon Sep 17 00:00:00 2001 From: FND Date: Tue, 31 Jan 2012 13:54:46 +0100 Subject: [PATCH 0068/2940] Hyphenate "class-based" makes it more readable --- CHANGES | 4 ++-- docs/api.rst | 2 +- docs/extensiondev.rst | 4 ++-- docs/views.rst | 4 ++-- flask/views.py | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 970f183a..7188e57b 100644 --- a/CHANGES +++ b/CHANGES @@ -89,7 +89,7 @@ Released on September 29th 2011, codename Rakija variable as well as ``SERVER_NAME`` are now properly used by the test client as defaults. - Added :attr:`flask.views.View.decorators` to support simpler decorating of - pluggable (class based) views. + pluggable (class-based) views. - Fixed an issue where the test client if used with the "with" statement did not trigger the execution of the teardown handlers. - Added finer control over the session cookie parameters. @@ -177,7 +177,7 @@ Released on June 28th 2011, codename Grappa might occur during request processing (for instance database connection errors, timeouts from remote resources etc.). - Blueprints can provide blueprint specific error handlers. -- Implemented generic :ref:`views` (class based views). +- Implemented generic :ref:`views` (class-based views). Version 0.6.1 ------------- diff --git a/docs/api.rst b/docs/api.rst index d2b62199..ec7e4f63 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -476,7 +476,7 @@ Signals .. _blinker: http://pypi.python.org/pypi/blinker -Class Based Views +Class-Based Views ----------------- .. versionadded:: 0.7 diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index ee0d5e60..074d06ab 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -148,7 +148,7 @@ classes: a remote application that uses OAuth. 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 a +we will use the class-based approach because it will provide users with a manager object that handles opening and closing database connections. The Extension Code @@ -203,7 +203,7 @@ So here's what these lines of code do: 5. Finally, we add a `get_db` function that simplifies access to the context's database. -So why did we decide on a class based approach here? Because using our +So why did we decide on a class-based approach here? Because using our extension looks something like this:: from flask import Flask diff --git a/docs/views.rst b/docs/views.rst index 9270921b..02c62704 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -23,7 +23,7 @@ database and renders into a template:: This is simple and flexible, but if you want to provide this view in a generic fashion that can be adapted to other models and templates as well -you might want more flexibility. This is where pluggable class based +you might want more flexibility. This is where pluggable class-based views come into place. As the first step to convert this into a class based view you would do this:: @@ -70,7 +70,7 @@ this by itself is not helpful, so let's refactor the code a bit:: return User.query.all() This of course is not that helpful for such a small example, but it's good -enough to explain the basic principle. When you have a class based view +enough to explain the basic principle. When you have a class-based view the question comes up what `self` points to. The way this works is that whenever the request is dispatched a new instance of the class is created and the :meth:`~flask.views.View.dispatch_request` method is called with diff --git a/flask/views.py b/flask/views.py index 2aaaf156..79d62992 100644 --- a/flask/views.py +++ b/flask/views.py @@ -3,7 +3,7 @@ flask.views ~~~~~~~~~~~ - This module provides class based views inspired by the ones in Django. + This module provides class-based views inspired by the ones in Django. :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. @@ -50,7 +50,7 @@ class View(object): #: A for which methods this pluggable view can handle. methods = None - #: The canonical way to decorate class based views is to decorate the + #: The canonical way to decorate class-based views is to decorate the #: return value of as_view(). However since this moves parts of the #: logic from the class declaration to the place where it's hooked #: into the routing system. @@ -89,7 +89,7 @@ class View(object): view = decorator(view) # we attach the view class to the view function for two reasons: - # first of all it allows us to easily figure out what class based + # first of all it allows us to easily figure out what class-based # view this thing came from, secondly it's also used for instantiating # the view class so you can actually replace it with something else # for testing purposes and debugging. @@ -120,7 +120,7 @@ class MethodViewType(type): class MethodView(View): - """Like a regular class based view but that dispatches requests to + """Like a regular class-based view but that dispatches requests to particular methods. For instance if you implement a method called :meth:`get` it means you will response to ``'GET'`` requests and the :meth:`dispatch_request` implementation will automatically From d33f9990c80bd25fd36e19ea4d010fe0a1a56c42 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Wed, 1 Feb 2012 14:49:46 +0200 Subject: [PATCH 0069/2940] Document context processors' variable functions --- docs/templating.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/templating.rst b/docs/templating.rst index bd940b0e..15433f2a 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -186,3 +186,22 @@ The context processor above makes a variable called `user` available in the template with the value of `g.user`. This example is not very interesting because `g` is available in templates anyways, but it gives an idea how this works. + +It is also possible to inject functions that can have any number of +arguments:: + + @app.context_processor + def price_formatter(): + def loader(amount, currency=u'€'): + return u'{0:.2f}{1}.format(amount, currency) + return dict(format_price=loader) + +The above construct registers a "variable" function called +`format_price` which can then be used in template:: + + {{ format_price(0.33) }} + +The difference from regular context processor' variables is that functions +are evaluated upon template rendering compared to variables whose values +are created during `app` startup . Therefore "variable" functions make it +possible to inject dynamic data into templates. From 8ef2ca99b991f66f0b61975e883325fcf5c13046 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 1 Feb 2012 18:03:29 -0500 Subject: [PATCH 0070/2940] Reword context processors for functions. --- docs/templating.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/templating.rst b/docs/templating.rst index 15433f2a..19217528 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -147,6 +147,8 @@ autoescape %}`` block: Whenever you do this, please be very cautious about the variables you are using in this block. +.. _registering-filters: + Registering Filters ------------------- @@ -176,7 +178,7 @@ context processors exist in Flask. Context processors run before the template is rendered and have the ability to inject new values into the template context. A context processor is a function that returns a dictionary. The keys and values of this dictionary are then merged with -the template context:: +the template context, for all templates in the app:: @app.context_processor def inject_user(): @@ -187,21 +189,21 @@ the template with the value of `g.user`. This example is not very interesting because `g` is available in templates anyways, but it gives an idea how this works. -It is also possible to inject functions that can have any number of -arguments:: +Variables are not limited to values; a context processor can also make +functions available to templates (since Python allows passing around +functions):: @app.context_processor - def price_formatter(): - def loader(amount, currency=u'€'): + def utility_processor(): + def format_price(amount, currency=u'€'): return u'{0:.2f}{1}.format(amount, currency) - return dict(format_price=loader) + return dict(format_price=format_price) -The above construct registers a "variable" function called -`format_price` which can then be used in template:: +The context processor above makes the `format_price` function available to all +templates:: {{ format_price(0.33) }} -The difference from regular context processor' variables is that functions -are evaluated upon template rendering compared to variables whose values -are created during `app` startup . Therefore "variable" functions make it -possible to inject dynamic data into templates. +You could also build `format_price` as a template filter (see +:ref:`registering-filters`), but this demonstrates how to pass functions in a +context processor. From 5a1bef4429f289eac52f8ccf57a1a8409898d489 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 1 Feb 2012 18:03:32 -0500 Subject: [PATCH 0071/2940] Demonstrate in docs how to use registered filters. Requested by @plaes on #pocoo irc. --- docs/templating.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index 19217528..d4878cdb 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -168,7 +168,13 @@ The two following examples work the same and both reverse an object:: app.jinja_env.filters['reverse'] = reverse_filter In case of the decorator the argument is optional if you want to use the -function name as name of the filter. +function name as name of the filter. Once registered, you can use the filter +in your templates in the same way as Jinja2's builtin filters, for example if +you have a Python list in context called `mylist`:: + + {% for x in mylist | reverse %} + {% endfor %} + Context Processors ------------------ From dfd3ef6d5460d666226d1831de48cdaff72c1bb6 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 3 Feb 2012 13:19:04 -0500 Subject: [PATCH 0072/2940] Add cookie size limit note to sessions section. Result of discussion with jujule_ on #pocoo irc. --- docs/quickstart.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 368bd96c..1d524a09 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -798,6 +798,13 @@ not using the template engine (as in this example). Just take that thing and copy/paste it into your code and you're done. +A note on cookie-based sessions: Flask will take the values you put into the +session object and serialize them into a cookie. If you are finding some +values do not persist across requests, cookies are indeed enabled, and you are +not getting a clear error message, check the size of the cookie in your page +responses compared to the size supported by web browsers. + + Message Flashing ---------------- From 69e7a0a2a0391ed1bdd102deffbd98137790f959 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 3 Feb 2012 18:11:14 -0500 Subject: [PATCH 0073/2940] Move debugger details into a new section, #343. --- docs/errorhandling.rst | 69 ++++++++++++++++++++++++++++++++++++++++-- docs/quickstart.rst | 39 ++---------------------- 2 files changed, 70 insertions(+), 38 deletions(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index debb9d75..97ff4df2 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -1,7 +1,7 @@ .. _application-errors: -Handling Application Errors -=========================== +Logging Application Errors +========================== .. versionadded:: 0.3 @@ -235,3 +235,68 @@ iterating over them to attach handlers:: for logger in loggers: logger.addHandler(mail_handler) logger.addHandler(file_handler) + + +Debugging Application Errors +============================ + +For production applications, configure your application with logging and +notifications as described in :ref:`application-errors`. This section provides +pointers when debugging deployment configuration and digging deeper with a +full-featured Python debugger. + + +When in Doubt, Run Manually +--------------------------- + +Having problems getting your application configured for production? If you +have shell access to your host, verify that you can run your application +manually from the shell in the deployment environment. Be sure to run under +the same user account as the configured deployment to troubleshoot permission +issues. You can use Flask's builtin development server with `debug=True` on +your production host, which is helpful in catching configuration issues, but +**be sure to do this temporarily in a controlled environment.** Do not run in +production with `debug=True`. + + +.. _working-with-debuggers: + +Working with Debuggers +---------------------- + +To dig deeper, possibly to trace code execution, Flask provides a debugger out +of the box (see :ref:`debug-mode`). If you would like to use another Python +debugger, note that debuggers interfere with each other. You have to set some +options in order to use your favorite debugger: + +* ``debug`` - whether to enable debug mode and catch exceptinos +* ``use_debugger`` - whether to use the internal Flask debugger +* ``use_reloader`` - whether to reload and fork the process on exception + +``debug`` must be True (i.e., exceptions must be caught) in order for the other +two options to have any value. + +If you're using Aptana/Eclipse for debugging you'll need to set both +``use_debugger`` and ``use_reloader`` to False. + +A possible useful pattern for configuration is to set the following in your +config.yaml (change the block as approriate for your application, of course):: + + FLASK: + DEBUG: True + DEBUG_WITH_APTANA: True + +Then in your application's entry-point (main.py), you could have something like:: + + if __name__ == "__main__": + # To allow aptana to receive errors, set use_debugger=False + app = create_app(config="config.yaml") + + if app.debug: use_debugger = True + try: + # Disable Flask's debugger if external debugger is requested + use_debugger = not(app.config.get('DEBUG_WITH_APTANA')) + except: + pass + app.run(use_debugger=use_debugger, debug=app.debug, + use_reloader=use_debugger, host='0.0.0.0') diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1d524a09..9fde0c2f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -77,6 +77,8 @@ To stop the server, hit control-C. This tells your operating system to listen on all public IPs. +.. _debug-mode: + Debug Mode ---------- @@ -112,42 +114,7 @@ Screenshot of the debugger in action: :class: screenshot :alt: screenshot of debugger in action -.. admonition:: Working With Other Debuggers - - Debuggers interfere with each other. - That said, you may still wish to use the debugger in a tool of your choice. - Flask provides the following options to manage the debug process: - - * ``debug`` - whether to enable debug mode and catch exceptinos - * ``use_debugger`` - whether to use the internal Flask debugger - * ``use_reloader`` - whether to reload and fork the process on exception - - ``debug`` must be True (i.e., exceptions must be caught) in order for the - other two options to have any value. - - If you're using Aptana/Eclipse for debugging you'll need to set both - ``use_debugger`` and ``use_reloader`` to False. - - A possible useful pattern for configuration is to set the following in your - config.yaml (change the block as approriate for your application, of course):: - - FLASK: - DEBUG: True - DEBUG_WITH_APTANA: True - - Then in your application's entry-point (main.py), you could have something like:: - - if __name__ == "__main__": - # To allow aptana to receive errors, set use_debugger=False - app = create_app(config="config.yaml") - - if app.debug: use_debugger = True - try: - # Disable Flask's debugger if external debugger is requested - use_debugger = not(app.config.get('DEBUG_WITH_APTANA')) - except: - pass - app.run(use_debugger=use_debugger, debug=app.debug, use_reloader=use_debugger, host='0.0.0.0') +Have another debugger in mind? See :ref:`working-with-debuggers`. Routing From 84b96ac9d398afd5a620c48cfe9e969b95e6577a Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 4 Feb 2012 10:37:58 -0500 Subject: [PATCH 0074/2940] Add pypy to tox.ini. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 15911b4c..d9db8990 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py25,py26,py27 +envlist=py25,py26,py27,pypy [testenv] commands=make test From 96d7f207878bcd561c4085312eb9e235b9dc4d2d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 4 Feb 2012 10:44:16 -0500 Subject: [PATCH 0075/2940] Fix tox warning, test "not installed in testenv". --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d9db8990..91c6d664 100644 --- a/tox.ini +++ b/tox.ini @@ -2,4 +2,4 @@ envlist=py25,py26,py27,pypy [testenv] -commands=make test +commands=python run-tests.py From 3de8de1985f46297243eab340abd6c45c82bb9c4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:19:32 -0500 Subject: [PATCH 0076/2940] pip > easy_install --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d41a3eca..3a302cea 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ And Easy to Setup :: - $ easy_install Flask + $ pip install Flask $ python hello.py * Running on http://localhost:5000/ From 4a75198f36625b472215a4ef6192c41f950fa202 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:28:17 -0500 Subject: [PATCH 0077/2940] pip and distribute installation --- docs/installation.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 46f43186..0d3fcd74 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -100,7 +100,7 @@ System-Wide Installation This is possible as well, though I do not recommend it. Just run `easy_install` with root privileges:: - $ sudo easy_install Flask + $ sudo pip install Flask (On Windows systems, run it in a command-prompt window with administrator privleges, and leave out `sudo`.) @@ -110,7 +110,7 @@ Living on the Edge ------------------ If you want to work with the latest version of Flask, there are two ways: you -can either let `easy_install` pull in the development version, or you can tell +can either let `pip` pull in the development version, or you can tell it to operate on a git checkout. Either way, virtualenv is recommended. Get the git checkout in a new virtualenv and run in development mode:: @@ -138,19 +138,19 @@ To just get the development version without git, do this instead:: $ . env/bin/activate New python executable in env/bin/python Installing setuptools............done. - $ easy_install Flask==dev + $ pip install Flask==dev ... Finished processing dependencies for Flask==dev .. _windows-easy-install: -`easy_install` on Windows -------------------------- +`pip` and `distribute` on Windows +----------------------------------- On Windows, installation of `easy_install` is a little bit trickier, but still -quite easy. The easiest way to do it is to download the `ez_setup.py`_ file -and run it. The easiest way to run the file is to open your downloads folder -and double-click on the file. +quite easy. The easiest way to do it is to download the +`distribute_setup.py`_ file and run it. The easiest way to run the file is to +open your downloads folder and double-click on the file. Next, add the `easy_install` command and other Python scripts to the command search path, by adding your Python installation's Scripts folder @@ -161,14 +161,18 @@ Then click on "Advanced System settings" (in Windows XP, click on the Finally, double-click on the "Path" variable in the "System variables" section, and add the path of your Python interpreter's Scripts folder. Be sure to delimit it from existing values with a semicolon. Assuming you are using -Python 2.6 on the default path, add the following value:: +Python 2.7 on the default path, add the following value:: - ;C:\Python26\Scripts + ;C:\Python27\Scripts And you are done! To check that it worked, open the Command Prompt and execute ``easy_install``. If you have User Account Control enabled on Windows Vista or Windows 7, it should prompt you for administrator privileges. +Now that you have ``easy_install``, you can use it to install ``pip``:: -.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py + > easy_install pip + + +.. _distribute_setup.py: http://python-distribute.org/distribute_setup.py From 73a859533525febe42c0645ec25eeaa049123973 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:41:13 -0500 Subject: [PATCH 0078/2940] vent and no more . --- docs/installation.rst | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 0d3fcd74..075f12cb 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -66,22 +66,18 @@ folder within:: $ mkdir myproject $ cd myproject - $ virtualenv env - New python executable in env/bin/python + $ virtualenv venv + New python executable in venv/bin/python Installing setuptools............done. Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ . env/bin/activate - -(Note the space between the dot and the script name. The dot means that this -script should run in the context of the current shell. If this command does -not work in your shell, try replacing the dot with ``source``.) + $ source venv/bin/activate If you are a Windows user, the following command is for you:: - $ env\scripts\activate + $ venv\scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). @@ -89,7 +85,7 @@ your shell has changed to show the active environment). Now you can just enter the following command to get Flask activated in your virtualenv:: - $ easy_install Flask + $ pip install Flask A few seconds later and you are good to go. From 60f7e3a7b463db68f3cc22085b6c5eaac985e7f7 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 20:41:13 -0500 Subject: [PATCH 0079/2940] vent and no more . --- docs/installation.rst | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 0d3fcd74..71c4b59c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -66,22 +66,18 @@ folder within:: $ mkdir myproject $ cd myproject - $ virtualenv env - New python executable in env/bin/python + $ virtualenv venv + New python executable in venv/bin/python Installing setuptools............done. Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ . env/bin/activate - -(Note the space between the dot and the script name. The dot means that this -script should run in the context of the current shell. If this command does -not work in your shell, try replacing the dot with ``source``.) + $ source venv/bin/activate If you are a Windows user, the following command is for you:: - $ env\scripts\activate + $ venv\scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). @@ -89,7 +85,7 @@ your shell has changed to show the active environment). Now you can just enter the following command to get Flask activated in your virtualenv:: - $ easy_install Flask + $ pip install Flask A few seconds later and you are good to go. @@ -118,10 +114,10 @@ Get the git checkout in a new virtualenv and run in development mode:: $ git clone http://github.com/mitsuhiko/flask.git Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask - $ virtualenv env - $ . env/bin/activate + $ virtualenv venv --distribute New python executable in env/bin/python Installing setuptools............done. + $ source env/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask @@ -134,10 +130,10 @@ To just get the development version without git, do this instead:: $ mkdir flask $ cd flask - $ virtualenv env - $ . env/bin/activate - New python executable in env/bin/python - Installing setuptools............done. + $ virtualenv venv --distribute + $ source venv/bin/activate + New python executable in venv/bin/python + Installing distribute............done. $ pip install Flask==dev ... Finished processing dependencies for Flask==dev From 5e848176e5b4db2958614e94024bd742830446eb Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Feb 2012 21:05:51 -0500 Subject: [PATCH 0080/2940] typos and fixes --- docs/installation.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 71c4b59c..a5fe6562 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -68,7 +68,7 @@ folder within:: $ cd myproject $ virtualenv venv New python executable in venv/bin/python - Installing setuptools............done. + Installing distribute............done. Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: @@ -99,7 +99,7 @@ This is possible as well, though I do not recommend it. Just run $ sudo pip install Flask (On Windows systems, run it in a command-prompt window with administrator -privleges, and leave out `sudo`.) +privileges, and leave out `sudo`.) Living on the Edge @@ -115,9 +115,9 @@ Get the git checkout in a new virtualenv and run in development mode:: Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask $ virtualenv venv --distribute - New python executable in env/bin/python - Installing setuptools............done. - $ source env/bin/activate + New python executable in venv/bin/python + Installing distribute............done. + $ source venv/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask From e070ede050fdb2ce155e13e29f5588c9831fa6b5 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 7 Feb 2012 10:42:25 -0500 Subject: [PATCH 0081/2940] Use "." not "source" for shell sourcing. Shell portability from mitsuhiko. --- docs/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a5fe6562..8e6a4497 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -73,7 +73,7 @@ folder within:: Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:: - $ source venv/bin/activate + $ . venv/bin/activate If you are a Windows user, the following command is for you:: @@ -117,7 +117,7 @@ Get the git checkout in a new virtualenv and run in development mode:: $ virtualenv venv --distribute New python executable in venv/bin/python Installing distribute............done. - $ source venv/bin/activate + $ . venv/bin/activate $ python setup.py develop ... Finished processing dependencies for Flask @@ -131,7 +131,7 @@ To just get the development version without git, do this instead:: $ mkdir flask $ cd flask $ virtualenv venv --distribute - $ source venv/bin/activate + $ . venv/bin/activate New python executable in venv/bin/python Installing distribute............done. $ pip install Flask==dev From 04bb720d3813c8cfe3ae46d2fb85dbc9a03a9b70 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 14 Feb 2012 18:13:29 -0500 Subject: [PATCH 0082/2940] Fix Blueprint example with template_folder, #403. --- docs/blueprints.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 9422fd02..4e3888c2 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -61,7 +61,8 @@ implement a blueprint that does simple rendering of static templates:: from flask import Blueprint, render_template, abort from jinja2 import TemplateNotFound - simple_page = Blueprint('simple_page', __name__) + simple_page = Blueprint('simple_page', __name__, + template_folder='templates') @simple_page.route('/', defaults={'page': 'index'}) @simple_page.route('/') From 0b3369355dadb39ac1ce9580d95004233031a287 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Fri, 24 Feb 2012 00:46:20 -0600 Subject: [PATCH 0083/2940] Allow loading template from iterable --- flask/templating.py | 8 +++++--- flask/testsuite/templating.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index 90e8772a..c809a63f 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -109,17 +109,19 @@ def _render(template, context, app): return rv -def render_template(template_name, **context): +def render_template(template_name_or_list, **context): """Renders a template from the template folder with the given context. - :param template_name: the name of the template to be rendered + :param template_name_or_list: the name of the template to be + rendered, or an iterable with template names + the first one existing will be rendered :param context: the variables that should be available in the context of the template. """ ctx = _request_ctx_stack.top ctx.app.update_template_context(context) - return _render(ctx.app.jinja_env.get_template(template_name), + return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list), context, ctx.app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 759fe0f3..4a0ebdbc 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -178,6 +178,25 @@ class TemplatingTestCase(FlaskTestCase): self.assert_equal(rv.data, 'Hello Custom World!') + def test_iterable_loader(self): + app = flask.Flask(__name__) + @app.context_processor + def context_processor(): + return {'whiskey': 'Jameson'} + @app.route('/') + def index(): + return flask.render_template( + ['no_template.xml', # should skip this one + 'simple_template.html', # should render this + 'context_template.html'], + value=23) + + rv = app.test_client().get('/') + self.assert_equal(rv.data, '

Jameson

') + + + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TemplatingTestCase)) From fdad6713eba3f7aaae6ca4f6f50bb8b72452c6d9 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Fri, 24 Feb 2012 09:33:11 -0500 Subject: [PATCH 0084/2940] Add updates to render_template to CHANGES, #409. --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 7188e57b..8311e2e7 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,9 @@ Relase date to be decided, codename to be chosen. using configuration values, e.g. ``app.run(app.config.get('MYHOST'), app.config.get('MYPORT'))``, with proper behavior whether or not a config file is provided. +- The :meth:`flask.render_template` method now accepts a either an iterable of + template names or a single template name. Previously, it only accepted a + single template name. On an iterable, the first template found is rendered. Version 0.8.1 From fc7fe628466857fd8b83c1b2b8501df2d15ae4a9 Mon Sep 17 00:00:00 2001 From: Andrew Ash Date: Wed, 22 Feb 2012 22:50:26 -0800 Subject: [PATCH 0085/2940] Update docs/errorhandling.rst --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 97ff4df2..9e26196d 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -280,7 +280,7 @@ If you're using Aptana/Eclipse for debugging you'll need to set both ``use_debugger`` and ``use_reloader`` to False. A possible useful pattern for configuration is to set the following in your -config.yaml (change the block as approriate for your application, of course):: +config.yaml (change the block as appropriate for your application, of course):: FLASK: DEBUG: True From 20a3281209a5e99271c911f9c1143b1e6f0fb0b5 Mon Sep 17 00:00:00 2001 From: awsum Date: Tue, 21 Feb 2012 22:04:36 +0200 Subject: [PATCH 0086/2940] Update docs/patterns/wtforms.rst --- docs/patterns/wtforms.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index ed530427..1bf46637 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -85,8 +85,10 @@ Here's an example `_formhelpers.html` template with such a macro:
{{ field.label }}
{{ field(**kwargs)|safe }} {% if field.errors %} -
    - {% for error in field.errors %}
  • {{ error }}{% endfor %} +
      + {% for error in field.errors %} +
    • {{ error }}
    • + {% endfor %}
    {% endif %}
@@ -106,7 +108,7 @@ takes advantage of the `_formhelpers.html` template: .. sourcecode:: html+jinja {% from "_formhelpers.html" import render_field %} -
+
{{ render_field(form.username) }} {{ render_field(form.email) }} From b9f4e0bd9c6e200ae930c2d9c07c582be118b051 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Sun, 26 Feb 2012 12:43:50 -0800 Subject: [PATCH 0087/2940] Remove redundant words from quickstart. --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9fde0c2f..f23f957d 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -168,8 +168,8 @@ The following converters exist: .. admonition:: Unique URLs / Redirection Behaviour - Flask's URL rules are based on Werkzeug's routing module. The idea behind - that module is to ensure beautiful and unique also unique URLs based on + Flask's URL rules are based on Werkzeug's routing module. The idea + behind that module is to ensure beautiful and unique URLs based on precedents laid down by Apache and earlier HTTP servers. Take these two rules:: @@ -234,7 +234,7 @@ some examples: (This also uses the :meth:`~flask.Flask.test_request_context` method, explained below. It tells Flask to behave as though it is handling a request, even -though were are interacting with it through a Python shell. Have a look at the +though we are interacting with it through a Python shell. Have a look at the explanation below. :ref:`context-locals`). Why would you want to build URLs instead of hard-coding them into your From 85ad4ffb605eee37cae9ffa1f82c5b6bb95c7820 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Thu, 1 Mar 2012 02:07:26 -0600 Subject: [PATCH 0088/2940] Blueprint example app --- examples/blueprintexample/blueprintexample.py | 11 ++++++++ .../blueprintexample/simple_page/__init__.py | 0 .../simple_page/simple_page.py | 13 ++++++++++ .../simple_page/templates/pages/hello.html | 5 ++++ .../simple_page/templates/pages/index.html | 5 ++++ .../simple_page/templates/pages/layout.html | 25 +++++++++++++++++++ .../simple_page/templates/pages/world.html | 5 ++++ 7 files changed, 64 insertions(+) create mode 100644 examples/blueprintexample/blueprintexample.py create mode 100644 examples/blueprintexample/simple_page/__init__.py create mode 100644 examples/blueprintexample/simple_page/simple_page.py create mode 100644 examples/blueprintexample/simple_page/templates/pages/hello.html create mode 100644 examples/blueprintexample/simple_page/templates/pages/index.html create mode 100644 examples/blueprintexample/simple_page/templates/pages/layout.html create mode 100644 examples/blueprintexample/simple_page/templates/pages/world.html diff --git a/examples/blueprintexample/blueprintexample.py b/examples/blueprintexample/blueprintexample.py new file mode 100644 index 00000000..bc0e41d4 --- /dev/null +++ b/examples/blueprintexample/blueprintexample.py @@ -0,0 +1,11 @@ +from flask import Flask +from simple_page.simple_page import simple_page + +app = Flask(__name__) +app.register_blueprint(simple_page) +# Blueprint can be registered many times +app.register_blueprint(simple_page, url_prefix='/pages') + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/examples/blueprintexample/simple_page/__init__.py b/examples/blueprintexample/simple_page/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/blueprintexample/simple_page/simple_page.py b/examples/blueprintexample/simple_page/simple_page.py new file mode 100644 index 00000000..cb82cc37 --- /dev/null +++ b/examples/blueprintexample/simple_page/simple_page.py @@ -0,0 +1,13 @@ +from flask import Blueprint, render_template, abort +from jinja2 import TemplateNotFound + +simple_page = Blueprint('simple_page', __name__, + template_folder='templates') + +@simple_page.route('/', defaults={'page': 'index'}) +@simple_page.route('/') +def show(page): + try: + return render_template('pages/%s.html' % page) + except TemplateNotFound: + abort(404) diff --git a/examples/blueprintexample/simple_page/templates/pages/hello.html b/examples/blueprintexample/simple_page/templates/pages/hello.html new file mode 100644 index 00000000..7fca6668 --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/hello.html @@ -0,0 +1,5 @@ +{% extends "pages/layout.html" %} + +{% block body %} + Hello +{% endblock %} \ No newline at end of file diff --git a/examples/blueprintexample/simple_page/templates/pages/index.html b/examples/blueprintexample/simple_page/templates/pages/index.html new file mode 100644 index 00000000..0ca3ffe2 --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/index.html @@ -0,0 +1,5 @@ +{% extends "pages/layout.html" %} + +{% block body %} + Blueprint example page +{% endblock %} \ No newline at end of file diff --git a/examples/blueprintexample/simple_page/templates/pages/layout.html b/examples/blueprintexample/simple_page/templates/pages/layout.html new file mode 100644 index 00000000..2efccb95 --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/layout.html @@ -0,0 +1,25 @@ + +Simple Page Blueprint +
+

This is blueprint example

+

+ A simple page blueprint is registered under / and /pages
+ you can access it using this urls: +

+

+

+ Also you can register the same blueprint under another path +

+

+ + + + {% block body %} + {% endblock %} +
\ No newline at end of file diff --git a/examples/blueprintexample/simple_page/templates/pages/world.html b/examples/blueprintexample/simple_page/templates/pages/world.html new file mode 100644 index 00000000..bdb5b16b --- /dev/null +++ b/examples/blueprintexample/simple_page/templates/pages/world.html @@ -0,0 +1,5 @@ +{% extends "pages/layout.html" %} + +{% block body %} + World +{% endblock %} \ No newline at end of file From 62621ccd133ffcbe2c88d18841c0669d03739ac0 Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Thu, 1 Mar 2012 02:24:56 -0600 Subject: [PATCH 0089/2940] Blueprint example tests --- .../blueprintexample/blueprintexample_test.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 examples/blueprintexample/blueprintexample_test.py diff --git a/examples/blueprintexample/blueprintexample_test.py b/examples/blueprintexample/blueprintexample_test.py new file mode 100644 index 00000000..b8f93414 --- /dev/null +++ b/examples/blueprintexample/blueprintexample_test.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +""" + Blueprint Example Tests + ~~~~~~~~~~~~~~ + + Tests the Blueprint example app +""" +import blueprintexample +import unittest + + +class BlueprintExampleTestCase(unittest.TestCase): + + def setUp(self): + self.app = blueprintexample.app.test_client() + + def test_urls(self): + r = self.app.get('/') + self.assertEquals(r.status_code, 200) + + r = self.app.get('/hello') + self.assertEquals(r.status_code, 200) + + r = self.app.get('/world') + self.assertEquals(r.status_code, 200) + + #second blueprint instance + r = self.app.get('/pages/hello') + self.assertEquals(r.status_code, 200) + + r = self.app.get('/pages/world') + self.assertEquals(r.status_code, 200) + + +if __name__ == '__main__': + unittest.main() From 76773e1d0a8cabbe048bb76f9306185e9d83a85c Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Thu, 1 Mar 2012 08:34:08 -0500 Subject: [PATCH 0090/2940] Fixed silent keyword arg to config.from_envvar. The ``silent`` keyword argument to Config.from_envvar was not being honored if the environment variable existed but the file that it mentioned did not. The fix was simple - pass the keyword argument on to the underlying call to ``from_pyfile``. I also noticed that the return value from ``from_pyfile`` was not being passed back so I fixed that as well. --- flask/config.py | 3 +-- flask/testsuite/config.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/flask/config.py b/flask/config.py index 67dbf9b7..759fd488 100644 --- a/flask/config.py +++ b/flask/config.py @@ -106,8 +106,7 @@ class Config(dict): 'loaded. Set this variable and make it ' 'point to a configuration file' % variable_name) - self.from_pyfile(rv) - return True + return self.from_pyfile(rv, silent=silent) def from_pyfile(self, filename, silent=False): """Updates the values in the config from a Python file. This function diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 00f77cea..e10804c3 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -69,6 +69,24 @@ class ConfigTestCase(FlaskTestCase): finally: os.environ = env + def test_config_from_envvar_missing(self): + env = os.environ + try: + os.environ = {'FOO_SETTINGS': 'missing.cfg'} + try: + app = flask.Flask(__name__) + app.config.from_envvar('FOO_SETTINGS') + except IOError, e: + msg = str(e) + self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):')) + self.assert_(msg.endswith("missing.cfg'")) + else: + self.assert_(0, 'expected config') + self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) + finally: + os.environ = env + def test_config_missing(self): app = flask.Flask(__name__) try: From 8d7ca29a3554d20324ed9c75d9c095dfa8a8c439 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Thu, 1 Mar 2012 08:53:58 -0500 Subject: [PATCH 0091/2940] Cleaned up test case for issue #414. --- flask/testsuite/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index e10804c3..bf72925b 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -82,8 +82,8 @@ class ConfigTestCase(FlaskTestCase): 'file (No such file or directory):')) self.assert_(msg.endswith("missing.cfg'")) else: - self.assert_(0, 'expected config') - self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) + self.fail('expected IOError') + self.assertFalse(app.config.from_envvar('FOO_SETTINGS', silent=True)) finally: os.environ = env From 8445f0d939dc3c4a2e722dc6dd4938d02bc2e094 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Fri, 2 Mar 2012 07:46:39 -0300 Subject: [PATCH 0092/2940] Fixed assumption made on session implementations. In the snippet 'session.setdefault(...).append(...)', it was being assumed that changes made to mutable structures in the session are are always in sync with the session object, which is not true for session implementations that use a external storage for keeping their keys/values. --- flask/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 25250d26..122c330f 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -261,7 +261,9 @@ def flash(message, category='message'): messages and ``'warning'`` for warnings. However any kind of string can be used as category. """ - session.setdefault('_flashes', []).append((category, message)) + flashes = session.get('_flashes', []) + flashes.append((category, message)) + session['_flashes'] = flashes def get_flashed_messages(with_categories=False, category_filter=[]): From 7ed3cba6588fbd585b10e58e15e352e90874732d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 8 Mar 2012 09:14:14 -0800 Subject: [PATCH 0093/2940] Split ebook build process into own make target. --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6fa48693..08811ac2 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,6 @@ clean-pyc: find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + -# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html upload-docs: $(MAKE) -C docs html dirhtml latex epub $(MAKE) -C docs/_build/latex all-pdf @@ -31,7 +30,10 @@ upload-docs: rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub - @echo 'Building .mobi from .epub...' + +# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html +ebook: + @echo 'Using .epub from `make upload-docs` to create .mobi.' @echo 'Command `ebook-covert` is provided by calibre package.' @echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).' @echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.' From 9711fd402010b2e14a98879f0457174c3ca15a24 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 8 Mar 2012 16:41:39 -0500 Subject: [PATCH 0094/2940] On JSON requests, the JSON response should have Content-Type: application/json and the body of the response should be a JSON object. --- CHANGES | 2 ++ flask/testsuite/helpers.py | 12 ++++++++ flask/wrappers.py | 56 +++++++++++++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 8311e2e7..91a31040 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Version 0.9 Relase date to be decided, codename to be chosen. +- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted + response by default. - The :func:`flask.url_for` function now can generate anchors to the generated links. - The :func:`flask.url_for` function now can also explicitly generate diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 89c6c188..e48f8dc3 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -40,6 +40,18 @@ class JSONTestCase(FlaskTestCase): rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) + def test_json_bad_requests_content_type(self): + app = flask.Flask(__name__) + @app.route('/json', methods=['POST']) + def return_json(): + return unicode(flask.request.json) + c = app.test_client() + rv = c.post('/json', data='malformed', content_type='application/json') + self.assert_equal(rv.status_code, 400) + self.assert_equal(rv.mimetype, 'application/json') + self.assert_('description' in flask.json.loads(rv.data)) + self.assert_('

' not in flask.json.loads(rv.data)['description']) + def test_json_body_encoding(self): app = flask.Flask(__name__) app.testing = True diff --git a/flask/wrappers.py b/flask/wrappers.py index f6ec2788..3df697f7 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -10,7 +10,7 @@ """ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase -from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequest, HTTPException from werkzeug.utils import cached_property from .debughelpers import attach_enctype_error_multidict @@ -18,6 +18,43 @@ from .helpers import json, _assert_have_json from .globals import _request_ctx_stack +class JSONHTTPException(HTTPException): + """A base class for HTTP exceptions with ``Content-Type: + application/json``. + + The ``description`` attribute of this class must set to a string (*not* an + HTML string) which describes the error. + + """ + + def get_body(self, environ): + """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to + return the description of this error in JSON format instead of HTML. + + """ + return json.dumps(dict(description=self.get_description(environ))) + + def get_headers(self, environ): + """Returns a list of headers including ``Content-Type: + application/json``. + + """ + return [('Content-Type', 'application/json')] + + +class JSONBadRequest(JSONHTTPException, BadRequest): + """Represents an HTTP ``400 Bad Request`` error whose body contains an + error message in JSON format instead of HTML format (as in the superclass). + + """ + + #: The description of the error which occurred as a string. + description = ( + 'The browser (or proxy) sent a request that this server could not ' + 'understand.' + ) + + class Request(RequestBase): """The request object used by default in Flask. Remembers the matched endpoint and view arguments. @@ -108,12 +145,23 @@ class Request(RequestBase): def on_json_loading_failed(self, e): """Called if decoding of the JSON data failed. The return value of - this method is used by :attr:`json` when an error ocurred. The - default implementation raises a :class:`~werkzeug.exceptions.BadRequest`. + this method is used by :attr:`json` when an error ocurred. The default + implementation raises a :class:`JSONBadRequest`, which is a subclass of + :class:`~werkzeug.exceptions.BadRequest` which sets the + ``Content-Type`` to ``application/json`` and provides a JSON-formatted + error description:: + + {"description": "The browser (or proxy) sent a request that \ + this server could not understand."} + + .. versionchanged:: 0.9 + + Return a :class:`JSONBadRequest` instead of a + :class:`~werkzeug.exceptions.BadRequest` by default. .. versionadded:: 0.8 """ - raise BadRequest() + raise JSONBadRequest() def _load_form_data(self): RequestBase._load_form_data(self) From 6b9e6a5a52f22bdf6b86b76de1fc7c8e1a635a8f Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 11 Mar 2012 20:20:32 -0700 Subject: [PATCH 0095/2940] add heroku/deploy options to quickstart, and add more clear links in tutorial setup. --- docs/quickstart.rst | 36 ++++++++++++++++++++++++++++++++++++ docs/tutorial/setup.rst | 20 ++++++++++---------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9fde0c2f..ed11316c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -826,3 +826,39 @@ can do it like this:: from werkzeug.contrib.fixers import LighttpdCGIRootFix app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app) + +Share your Local Server with a Friend +------------------------------------- + +`Localtunnel `_ is a neat tool you can use to +quickly share your local Flask server to a friend. + +To install Localtunnel, open a terminal and run the following command:: + + sudo gem install localtunnel + +Then, with Flask running at ``http://localhost:5000``, open a new Terminal window +and type:: + + localtunnel 5000 + Port 5000 is now publicly accessible from http://54xy.localtunnel.com ... + +*(Get a* ``gem: command not found`` *error? Download RubyGems* +`here `_ *.)* + +If you load the URL given in the localtunnel output in your browser, you +should see your Flask app. It's actually being loaded from your own computer! + +Deploying to a Web Server +------------------------- + +`Heroku `_ offers a free web platform to host your +Flask app, and is the easiest way for you to put your Flask app online. +They have excellent instructions on how to deploy your Flask app `here +`_. + +Other resources for deploying Flask apps: + +- `Deploying Flask on ep.io `_ +- `Deploying Flask on Webfaction `_ +- `Deploying Flask on Google App Engine `_ diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index e9e4d679..3a8fba33 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -11,7 +11,7 @@ into the module which we will be doing here. However a cleaner solution would be to create a separate `.ini` or `.py` file and load that or import the values from there. -:: +In `flaskr.py`:: # all the imports import sqlite3 @@ -26,7 +26,7 @@ the values from there. PASSWORD = 'default' Next we can create our actual application and initialize it with the -config from the same file:: +config from the same file, in `flaskr.py`:: # create our little application :) app = Flask(__name__) @@ -37,21 +37,21 @@ string it will import it) and then look for all uppercase variables defined there. In our case, the configuration we just wrote a few lines of code above. You can also move that into a separate file. -It is also a good idea to be able to load a configuration from a -configurable file. This is what :meth:`~flask.Config.from_envvar` can -do:: +Usually, it is a good idea to load a configuration from a configurable +file. This is what :meth:`~flask.Config.from_envvar` can do, replacing the +:meth:`~flask.Config.from_object` line above:: app.config.from_envvar('FLASKR_SETTINGS', silent=True) That way someone can set an environment variable called -:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will -then override the default values. The silent switch just tells Flask to -not complain if no such environment key is set. +:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will then +override the default values. The silent switch just tells Flask to not complain +if no such environment key is set. The `secret_key` is needed to keep the client-side sessions secure. Choose that key wisely and as hard to guess and complex as possible. The -debug flag enables or disables the interactive debugger. Never leave -debug mode activated in a production system because it will allow users to +debug flag enables or disables the interactive debugger. *Never leave +debug mode activated in a production system*, because it will allow users to execute code on the server! We also add a method to easily connect to the database specified. That From 605d0ee34421fbc1dd714790ff016834c9b3f0cb Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 11 Mar 2012 20:33:43 -0700 Subject: [PATCH 0096/2940] update links --- docs/quickstart.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ed11316c..f1771503 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -852,12 +852,13 @@ should see your Flask app. It's actually being loaded from your own computer! Deploying to a Web Server ------------------------- -`Heroku `_ offers a free web platform to host your -Flask app, and is the easiest way for you to put your Flask app online. -They have excellent instructions on how to deploy your Flask app `here -`_. +If you want to make your Flask app available to the Internet at large, `Heroku +`_ is very easy to set up and will run small Flask +applications for free. `Check out their tutorial on how to deploy Flask apps on +their service `_. -Other resources for deploying Flask apps: +There are a number of other websites that will host your Flask app and make it +easy for you to do so. - `Deploying Flask on ep.io `_ - `Deploying Flask on Webfaction `_ From 075b6b11c8b1690d946b8839e6dc4eb8a8cb7e3c Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Sun, 11 Mar 2012 20:45:58 -0700 Subject: [PATCH 0097/2940] Fix issue 140 This allows for a view function to return something like: jsonify(error="error msg"), 400 --- CHANGES | 3 +++ flask/app.py | 16 +++++++++++++++- flask/testsuite/basic.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8311e2e7..dbf447db 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,9 @@ Relase date to be decided, codename to be chosen. - The :meth:`flask.render_template` method now accepts a either an iterable of template names or a single template name. Previously, it only accepted a single template name. On an iterable, the first template found is rendered. +- View functions can now return a tuple with the first instance being an + instance of :class:`flask.Response`. This allows for returning + ``jsonify(error="error msg"), 400`` from a view function. Version 0.8.1 diff --git a/flask/app.py b/flask/app.py index 15e432de..f3d7efcb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1361,7 +1361,21 @@ class Flask(_PackageBoundObject): if isinstance(rv, basestring): return self.response_class(rv) if isinstance(rv, tuple): - return self.response_class(*rv) + if len(rv) > 0 and isinstance(rv[0], self.response_class): + original = rv[0] + new_response = self.response_class('', *rv[1:]) + if len(rv) < 3: + # The args for the response class are + # response=None, status=None, headers=None, + # mimetype=None, content_type=None, ... + # so if there's at least 3 elements the rv + # tuple contains header information so the + # headers from rv[0] "win." + new_response.headers = original.headers + new_response.response = original.response + return new_response + else: + return self.response_class(*rv) return self.response_class.force_type(rv, request.environ) def create_url_adapter(self, request): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index e6a278e5..41efb196 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -659,6 +659,35 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(rv.data, 'W00t') self.assert_equal(rv.mimetype, 'text/html') + def test_make_response_with_response_instance(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.make_response( + flask.jsonify({'msg': 'W00t'}), 400) + self.assertEqual(rv.status_code, 400) + self.assertEqual(rv.data, + '{\n "msg": "W00t"\n}') + self.assertEqual(rv.mimetype, 'application/json') + + rv = flask.make_response( + flask.Response(''), 400) + self.assertEqual(rv.status_code, 400) + self.assertEqual(rv.data, '') + self.assertEqual(rv.mimetype, 'text/html') + + rv = flask.make_response( + flask.Response('', headers={'Content-Type': 'text/html'}), + 400, None, 'application/json') + self.assertEqual(rv.status_code, 400) + self.assertEqual(rv.headers['Content-Type'], 'application/json') + + rv = flask.make_response( + flask.Response('', mimetype='application/json'), + 400, {'Content-Type': 'text/html'}) + self.assertEqual(rv.status_code, 400) + self.assertEqual(rv.headers['Content-Type'], 'text/html') + + def test_url_generation(self): app = flask.Flask(__name__) @app.route('/hello/', methods=['POST']) From 2befab24c50a8f1a3417a7ce22e65608ba3905e1 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 11 Mar 2012 23:17:40 -0700 Subject: [PATCH 0098/2940] remove localtunnel things that were added to snippets --- docs/quickstart.rst | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index f1771503..de62e546 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -827,28 +827,6 @@ can do it like this:: from werkzeug.contrib.fixers import LighttpdCGIRootFix app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app) -Share your Local Server with a Friend -------------------------------------- - -`Localtunnel `_ is a neat tool you can use to -quickly share your local Flask server to a friend. - -To install Localtunnel, open a terminal and run the following command:: - - sudo gem install localtunnel - -Then, with Flask running at ``http://localhost:5000``, open a new Terminal window -and type:: - - localtunnel 5000 - Port 5000 is now publicly accessible from http://54xy.localtunnel.com ... - -*(Get a* ``gem: command not found`` *error? Download RubyGems* -`here `_ *.)* - -If you load the URL given in the localtunnel output in your browser, you -should see your Flask app. It's actually being loaded from your own computer! - Deploying to a Web Server ------------------------- @@ -863,3 +841,4 @@ easy for you to do so. - `Deploying Flask on ep.io `_ - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ +- `Sharing your Localhost Server with Localtunnel `_ From 06b224676d2f6c38fbf1f486f636e81a85016d45 Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Mon, 12 Mar 2012 11:19:17 -0400 Subject: [PATCH 0099/2940] Added _PackageBoundObject.get_static_file_options. This method receives the name of a static file that is going to be served up and generates a dict of options to use when serving the file. The default set is empty so code will fall back to the existing behavior if the method is not overridden. I needed this method to adjust the cache control headers for .js files that one of my applications was statically serving. The default expiration is buried in an argument to send_file and is set to 12 hours. There was no good way to adjust this value previously. --- flask/helpers.py | 11 +++++++++-- flask/testsuite/helpers.py | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 25250d26..4cb918d2 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -495,7 +495,8 @@ def send_from_directory(directory, filename, **options): filename = safe_join(directory, filename) if not os.path.isfile(filename): raise NotFound() - return send_file(filename, conditional=True, **options) + options.setdefault('conditional', True) + return send_file(filename, **options) def get_root_path(import_name): @@ -651,6 +652,11 @@ class _PackageBoundObject(object): return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) + def get_static_file_options(self, filename): + """Function used internally to determine what keyword arguments + to send to :func:`send_from_directory` for a specific file.""" + return {} + def send_static_file(self, filename): """Function used internally to send static files from the static folder to the browser. @@ -659,7 +665,8 @@ class _PackageBoundObject(object): """ if not self.has_static_folder: raise RuntimeError('No static folder for this object') - return send_from_directory(self.static_folder, filename) + return send_from_directory(self.static_folder, filename, + **self.get_static_file_options(filename)) def open_resource(self, resource, mode='rb'): """Opens a resource from the application's resource folder. To see diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 89c6c188..c88026d9 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -17,7 +17,7 @@ import unittest from logging import StreamHandler from StringIO import StringIO from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr -from werkzeug.http import parse_options_header +from werkzeug.http import parse_cache_control_header, parse_options_header def has_encoding(name): @@ -204,6 +204,30 @@ class SendfileTestCase(FlaskTestCase): self.assert_equal(value, 'attachment') self.assert_equal(options['filename'], 'index.txt') + def test_static_file(self): + app = flask.Flask(__name__) + # default cache timeout is 12 hours (hard-coded) + with app.test_request_context(): + rv = app.send_static_file('index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 12 * 60 * 60) + # override get_static_file_options with some new values and check them + class StaticFileApp(flask.Flask): + def __init__(self): + super(StaticFileApp, self).__init__(__name__) + def get_static_file_options(self, filename): + opts = super(StaticFileApp, self).get_static_file_options(filename) + opts['cache_timeout'] = 10 + # this test catches explicit inclusion of the conditional + # keyword arg in the guts + opts['conditional'] = True + return opts + app = StaticFileApp() + with app.test_request_context(): + rv = app.send_static_file('index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 10) + class LoggingTestCase(FlaskTestCase): From 2d237f3c533baa768b29d808f1850382542bbd2f Mon Sep 17 00:00:00 2001 From: Matt Dawson Date: Mon, 12 Mar 2012 10:57:00 -0700 Subject: [PATCH 0100/2940] Fix grammar in extension dev docs. --- docs/extensiondev.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 074d06ab..5a8d5b16 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -332,9 +332,9 @@ extension to be approved you have to follow these guidelines: 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, in case of ``python setup.py test`` - dependencies for tests alone can be specified in the `setup.py` - file. The test suite also has to be part of the distribution. + 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: From 8216e036e96594a4932d1f6a3569fcf39fe3c2bd Mon Sep 17 00:00:00 2001 From: Thibaud Morel Date: Mon, 12 Mar 2012 11:16:03 -0700 Subject: [PATCH 0101/2940] Specifying supported Python versions in setup.py metadata --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 3a302cea..812b2c8a 100644 --- a/setup.py +++ b/setup.py @@ -100,6 +100,9 @@ setup( 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ], From cb24646948a8c00c5b39f0d76bf75e153ac502b1 Mon Sep 17 00:00:00 2001 From: Christoph Heer Date: Tue, 17 Jan 2012 21:09:59 +0100 Subject: [PATCH 0102/2940] Add jsonp support inside of jsonify --- flask/helpers.py | 21 +++++++++++++++++++++ flask/testsuite/helpers.py | 14 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index 25250d26..ebe5fca5 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -117,9 +117,30 @@ def jsonify(*args, **kwargs): information about this, have a look at :ref:`json-security`. .. versionadded:: 0.2 + + .. versionadded:: 0.9 + If the argument ``padded`` true than the json object will pad for + JSONP calls like from jquery. The response mimetype will also change + to ``text/javascript``. + + The json object will pad as javascript function with the function name + from the request argument ``callback`` or ``jsonp``. If the argument + ``padded`` a string jsonify will look for the function name in the + request argument with the name which is equal to ``padded``. Is there + no function name it will fallback and use ``jsonp`` as function name. """ if __debug__: _assert_have_json() + if 'padded' in kwargs: + if isinstance(kwargs['padded'], str): + callback = request.args.get(kwargs['padded']) or 'jsonp' + else: + callback = request.args.get('callback') or \ + request.args.get('jsonp') or 'jsonp' + del kwargs['padded'] + json_str = json.dumps(dict(*args, **kwargs), indent=None) + content = str(callback) + "(" + json_str + ")" + return current_app.response_class(content, mimetype='text/javascript') return current_app.response_class(json.dumps(dict(*args, **kwargs), indent=None if request.is_xhr else 2), mimetype='application/json') diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 89c6c188..44ac9016 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -61,11 +61,25 @@ class JSONTestCase(FlaskTestCase): @app.route('/dict') def return_dict(): return flask.jsonify(d) + @app.route("/padded") + def return_padded_json(): + return flask.jsonify(d, padded=True) + @app.route("/padded_custom") + def return_padded_json_custom_callback(): + return flask.jsonify(d, padded='my_func_name') c = app.test_client() for url in '/kw', '/dict': rv = c.get(url) self.assert_equal(rv.mimetype, 'application/json') self.assert_equal(flask.json.loads(rv.data), d) + for get_arg in 'callback=funcName', 'jsonp=funcName': + rv = c.get('/padded?' + get_arg) + self.assert_( rv.data.startswith("funcName(") ) + self.assert_( rv.data.endswith(")") ) + rv_json = rv.data.split('(')[1].split(')')[0] + self.assert_equal(flask.json.loads(rv_json), d) + rv = c.get('/padded_custom?my_func_name=funcName') + self.assert_( rv.data.startswith("funcName(") ) def test_json_attr(self): app = flask.Flask(__name__) From 09370c3f1c808e9251292bc228b6bef4b1223e93 Mon Sep 17 00:00:00 2001 From: Ned Jackson Lovely Date: Mon, 12 Mar 2012 15:26:05 -0400 Subject: [PATCH 0103/2940] Clean up docs and review pull request #384 Spelunking through the issues at the PyCon sprints. --- flask/helpers.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index ebe5fca5..ee68ce95 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -119,15 +119,16 @@ def jsonify(*args, **kwargs): .. versionadded:: 0.2 .. versionadded:: 0.9 - If the argument ``padded`` true than the json object will pad for - JSONP calls like from jquery. The response mimetype will also change - to ``text/javascript``. + If the ``padded`` argument is true, the JSON object will be padded + for JSONP calls and the response mimetype will be changed to + ``text/javascript``. By default, the request arguments ``callback`` + and ``jsonp`` will be used as the name for the callback function. + This will work with jQuery and most other JavaScript libraries + by default. - The json object will pad as javascript function with the function name - from the request argument ``callback`` or ``jsonp``. If the argument - ``padded`` a string jsonify will look for the function name in the - request argument with the name which is equal to ``padded``. Is there - no function name it will fallback and use ``jsonp`` as function name. + If the ``padded`` argument is a string, jsonify will look for + the request argument with the same name and use that value as the + callback-function name. """ if __debug__: _assert_have_json() From 27194a01d8f7e4fd913811859ec4051ff99c52f4 Mon Sep 17 00:00:00 2001 From: Ned Jackson Lovely Date: Mon, 12 Mar 2012 16:02:53 -0400 Subject: [PATCH 0104/2940] Fix typo in docs. http://feedback.flask.pocoo.org/message/279 --- docs/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9fde0c2f..b442af28 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -169,8 +169,8 @@ The following converters exist: .. admonition:: Unique URLs / Redirection Behaviour Flask's URL rules are based on Werkzeug's routing module. The idea behind - that module is to ensure beautiful and unique also unique URLs based on - precedents laid down by Apache and earlier HTTP servers. + that module is to ensure beautiful and unique URLs based on precedents + laid down by Apache and earlier HTTP servers. Take these two rules:: From 68f93634de2e25afda209b710002e4c9159fd38e Mon Sep 17 00:00:00 2001 From: Ned Jackson Lovely Date: Mon, 12 Mar 2012 17:18:27 -0400 Subject: [PATCH 0105/2940] Second thoughts on mime type After further review, changing the mime type on jsonp responses from text/javascript to application/javascript, with a hat-tip to http://stackoverflow.com/questions/111302/best-content-type-to-serve-jsonp --- flask/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index ee68ce95..31a0f693 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -121,7 +121,7 @@ def jsonify(*args, **kwargs): .. versionadded:: 0.9 If the ``padded`` argument is true, the JSON object will be padded for JSONP calls and the response mimetype will be changed to - ``text/javascript``. By default, the request arguments ``callback`` + ``application/javascript``. By default, the request arguments ``callback`` and ``jsonp`` will be used as the name for the callback function. This will work with jQuery and most other JavaScript libraries by default. @@ -141,7 +141,7 @@ def jsonify(*args, **kwargs): del kwargs['padded'] json_str = json.dumps(dict(*args, **kwargs), indent=None) content = str(callback) + "(" + json_str + ")" - return current_app.response_class(content, mimetype='text/javascript') + return current_app.response_class(content, mimetype='application/javascript') return current_app.response_class(json.dumps(dict(*args, **kwargs), indent=None if request.is_xhr else 2), mimetype='application/json') From 71b351173b1440d6ca2dc36284d080fda2a22006 Mon Sep 17 00:00:00 2001 From: "Anton I. Sipos" Date: Mon, 12 Mar 2012 14:53:24 -0700 Subject: [PATCH 0106/2940] Move JSONHTTPException and JSONBadRequest to new module flask.exceptions. --- flask/exceptions.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ flask/wrappers.py | 39 +----------------------------------- 2 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 flask/exceptions.py diff --git a/flask/exceptions.py b/flask/exceptions.py new file mode 100644 index 00000000..9ccdedab --- /dev/null +++ b/flask/exceptions.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" + flask.exceptions + ~~~~~~~~~~~~ + + Flask specific additions to :class:`~werkzeug.exceptions.HTTPException` + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from werkzeug.exceptions import HTTPException, BadRequest +from .helpers import json + + +class JSONHTTPException(HTTPException): + """A base class for HTTP exceptions with ``Content-Type: + application/json``. + + The ``description`` attribute of this class must set to a string (*not* an + HTML string) which describes the error. + + """ + + def get_body(self, environ): + """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to + return the description of this error in JSON format instead of HTML. + + """ + return json.dumps(dict(description=self.get_description(environ))) + + def get_headers(self, environ): + """Returns a list of headers including ``Content-Type: + application/json``. + + """ + return [('Content-Type', 'application/json')] + + +class JSONBadRequest(JSONHTTPException, BadRequest): + """Represents an HTTP ``400 Bad Request`` error whose body contains an + error message in JSON format instead of HTML format (as in the superclass). + + """ + + #: The description of the error which occurred as a string. + description = ( + 'The browser (or proxy) sent a request that this server could not ' + 'understand.' + ) diff --git a/flask/wrappers.py b/flask/wrappers.py index 3df697f7..541d26ef 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -10,51 +10,14 @@ """ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase -from werkzeug.exceptions import BadRequest, HTTPException from werkzeug.utils import cached_property +from .exceptions import JSONBadRequest from .debughelpers import attach_enctype_error_multidict from .helpers import json, _assert_have_json from .globals import _request_ctx_stack -class JSONHTTPException(HTTPException): - """A base class for HTTP exceptions with ``Content-Type: - application/json``. - - The ``description`` attribute of this class must set to a string (*not* an - HTML string) which describes the error. - - """ - - def get_body(self, environ): - """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to - return the description of this error in JSON format instead of HTML. - - """ - return json.dumps(dict(description=self.get_description(environ))) - - def get_headers(self, environ): - """Returns a list of headers including ``Content-Type: - application/json``. - - """ - return [('Content-Type', 'application/json')] - - -class JSONBadRequest(JSONHTTPException, BadRequest): - """Represents an HTTP ``400 Bad Request`` error whose body contains an - error message in JSON format instead of HTML format (as in the superclass). - - """ - - #: The description of the error which occurred as a string. - description = ( - 'The browser (or proxy) sent a request that this server could not ' - 'understand.' - ) - - class Request(RequestBase): """The request object used by default in Flask. Remembers the matched endpoint and view arguments. From 74a72e86addd80a060f1abf9fe51bfc3f5d5be8b Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Mar 2012 14:58:26 -0700 Subject: [PATCH 0107/2940] Changed some things in the foreward to diminish its discouragement. --- docs/foreword.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 7678d014..539f2897 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -8,7 +8,7 @@ should or should not be using it. What does "micro" mean? ----------------------- -To me, the "micro" in microframework refers not only to the simplicity and +As Flask considers it, the "micro" in microframework refers not only to the simplicity and small size of the framework, but also the fact that it does not make many decisions for you. While Flask does pick a templating engine for you, we won't make such decisions for your datastore or other parts. @@ -55,7 +55,7 @@ section about :ref:`design`. Web Development is Dangerous ---------------------------- -I'm not joking. Well, maybe a little. If you write a web +If you write a web application, you are probably allowing users to register and leave their data on your server. The users are entrusting you with data. And even if you are the only user that might leave data in your application, you still From d8c2ec4cd863112af4c55e1044c8d3024d58f21a Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Mar 2012 15:03:26 -0700 Subject: [PATCH 0108/2940] Fixed linebreaks. --- docs/foreword.rst | 150 ++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 539f2897..1fa214e6 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -1,100 +1,104 @@ -Foreword +Foreword ======== -Read this before you get started with Flask. This hopefully answers some -questions about the purpose and goals of the project, and when you -should or should not be using it. +Read this before you get started with Flask. This hopefully answers +some questions about the purpose and goals of the project, and when +you should or should not be using it. -What does "micro" mean? +What does "micro" mean? ----------------------- -As Flask considers it, the "micro" in microframework refers not only to the simplicity and -small size of the framework, but also the fact that it does not make many -decisions for you. While Flask does pick a templating engine for you, we -won't make such decisions for your datastore or other parts. +As Flask considers it, the "micro" in microframework refers not only +to the simplicity and small size of the framework, but also the fact +that it does not make many decisions for you. While Flask does pick a +templating engine for you, we won't make such decisions for your +datastore or other parts. -However, to us the term “micro” does not mean that the whole implementation -has to fit into a single Python file. +However, to us the term “micro” does not mean that the whole +implementation has to fit into a single Python file. One of the design decisions with Flask was that simple tasks should be -simple; they should not take a lot of code and yet they should not limit you. -Because of that we made a few design choices that some people might find -surprising or unorthodox. For example, Flask uses thread-local objects -internally so that you don't have to pass objects around from function to -function within a request in order to stay threadsafe. While this is a -really easy approach and saves you a lot of time, it might also cause some -troubles for very large applications because changes on these thread-local -objects can happen anywhere in the same thread. In order to solve these -problems we don't hide the thread locals for you but instead embrace them -and provide you with a lot of tools to make it as pleasant as possible to -work with them. +simple; they should not take a lot of code and yet they should not +limit you. Because of that we made a few design choices that some +people might find surprising or unorthodox. For example, Flask uses +thread-local objects internally so that you don't have to pass objects +around from function to function within a request in order to stay +threadsafe. While this is a really easy approach and saves you a lot +of time, it might also cause some troubles for very large applications +because changes on these thread-local objects can happen anywhere in +the same thread. In order to solve these problems we don't hide the +thread locals for you but instead embrace them and provide you with a +lot of tools to make it as pleasant as possible to work with them. Flask is also based on convention over configuration, which means that -many things are preconfigured. For example, by convention templates and -static files are stored in subdirectories within the application's Python source tree. -While this can be changed you usually don't have to. +many things are preconfigured. For example, by convention templates +and static files are stored in subdirectories within the application's +Python source tree. While this can be changed you usually don't have +to. -The main reason Flask is called a "microframework" is the idea -to keep the core simple but extensible. There is no database abstraction +The main reason Flask is called a "microframework" is the idea to keep +the core simple but extensible. There is no database abstraction layer, no form validation or anything else where different libraries -already exist that can handle that. However Flask supports -extensions to add such functionality to your application as if it -was implemented in Flask itself. There are currently extensions for -object-relational mappers, form validation, upload handling, various open -authentication technologies and more. +already exist that can handle that. However Flask supports extensions +to add such functionality to your application as if it was implemented +in Flask itself. There are currently extensions for object-relational +mappers, form validation, upload handling, various open authentication +technologies and more. -Since Flask is based on a very solid foundation there is not a lot of code -in Flask itself. As such it's easy to adapt even for large applications -and we are making sure that you can either configure it as much as -possible by subclassing things or by forking the entire codebase. If you -are interested in that, check out the :ref:`becomingbig` chapter. +Since Flask is based on a very solid foundation there is not a lot of +code in Flask itself. As such it's easy to adapt even for large +applications and we are making sure that you can either configure it +as much as possible by subclassing things or by forking the entire +codebase. If you are interested in that, check out the +:ref:`becomingbig` chapter. If you are curious about the Flask design principles, head over to the section about :ref:`design`. -Web Development is Dangerous ----------------------------- +Web Development is Dangerous ---------------------------- -If you write a web -application, you are probably allowing users to register and leave their -data on your server. The users are entrusting you with data. And even if -you are the only user that might leave data in your application, you still -want that data to be stored securely. +If you write a web application, you are probably allowing users to +register and leave their data on your server. The users are +entrusting you with data. And even if you are the only user that +might leave data in your application, you still want that data to be +stored securely. -Unfortunately, there are many ways the security of a web application can be -compromised. Flask protects you against one of the most common security -problems of modern web applications: cross-site scripting (XSS). Unless -you deliberately mark insecure HTML as secure, Flask and the underlying -Jinja2 template engine have you covered. But there are many more ways to -cause security problems. +Unfortunately, there are many ways the security of a web application +can be compromised. Flask protects you against one of the most common +security problems of modern web applications: cross-site scripting +(XSS). Unless you deliberately mark insecure HTML as secure, Flask +and the underlying Jinja2 template engine have you covered. But there +are many more ways to cause security problems. The documentation will warn you about aspects of web development that -require attention to security. Some of these security concerns -are far more complex than one might think, and we all sometimes underestimate -the likelihood that a vulnerability will be exploited - until a clever -attacker figures out a way to exploit our applications. And don't think -that your application is not important enough to attract an attacker. -Depending on the kind of attack, chances are that automated bots are -probing for ways to fill your database with spam, links to malicious -software, and the like. +require attention to security. Some of these security concerns are +far more complex than one might think, and we all sometimes +underestimate the likelihood that a vulnerability will be exploited - +until a clever attacker figures out a way to exploit our applications. +And don't think that your application is not important enough to +attract an attacker. Depending on the kind of attack, chances are that +automated bots are probing for ways to fill your database with spam, +links to malicious software, and the like. So always keep security in mind when doing web development. -The Status of Python 3 +The Status of Python 3 ---------------------- -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 us 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. +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 us 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. -Werkzeug and Flask will be ported to Python 3 as soon as a solution for -the changes is found, and we will provide helpful tips how to upgrade -existing applications to Python 3. Until then, we strongly recommend -using Python 2.6 and 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 `_. +Werkzeug and Flask will be ported to Python 3 as soon as a solution +for the changes is found, and we will provide helpful tips how to +upgrade existing applications to Python 3. Until then, we strongly +recommend using Python 2.6 and 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 `_. From c78070d8623fb6f40bf4ef20a1109083ca79ef7a Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Mar 2012 15:08:52 -0700 Subject: [PATCH 0109/2940] Wrapped paragraphs; changed some words. --- docs/foreword.rst | 149 ++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 77 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 1fa214e6..5751ccb7 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -1,104 +1,99 @@ -Foreword +Foreword ======== -Read this before you get started with Flask. This hopefully answers -some questions about the purpose and goals of the project, and when -you should or should not be using it. +Read this before you get started with Flask. This hopefully answers some +questions about the purpose and goals of the project, and when you +should or should not be using it. -What does "micro" mean? +What does "micro" mean? ----------------------- -As Flask considers it, the "micro" in microframework refers not only -to the simplicity and small size of the framework, but also the fact -that it does not make many decisions for you. While Flask does pick a -templating engine for you, we won't make such decisions for your -datastore or other parts. +Flask considers the "micro" in microframework to refer not only to the +simplicity and small size of the framework, but also to the fact that it does +not make many decisions for you. While Flask does pick a templating engine +for you, we won't make such decisions for your datastore or other parts. -However, to us the term “micro” does not mean that the whole -implementation has to fit into a single Python file. +However, to us the term “micro” does not mean that the whole implementation +has to fit into a single Python file. One of the design decisions with Flask was that simple tasks should be -simple; they should not take a lot of code and yet they should not -limit you. Because of that we made a few design choices that some -people might find surprising or unorthodox. For example, Flask uses -thread-local objects internally so that you don't have to pass objects -around from function to function within a request in order to stay -threadsafe. While this is a really easy approach and saves you a lot -of time, it might also cause some troubles for very large applications -because changes on these thread-local objects can happen anywhere in -the same thread. In order to solve these problems we don't hide the -thread locals for you but instead embrace them and provide you with a -lot of tools to make it as pleasant as possible to work with them. +simple; they should not take a lot of code and yet they should not limit you. +Because of that we made a few design choices that some people might find +surprising or unorthodox. For example, Flask uses thread-local objects +internally so that you don't have to pass objects around from function to +function within a request in order to stay threadsafe. While this is a +really easy approach and saves you a lot of time, it might also cause some +troubles for very large applications because changes on these thread-local +objects can happen anywhere in the same thread. In order to solve these +problems we don't hide the thread locals for you but instead embrace them +and provide you with a lot of tools to make it as pleasant as possible to +work with them. Flask is also based on convention over configuration, which means that -many things are preconfigured. For example, by convention templates -and static files are stored in subdirectories within the application's -Python source tree. While this can be changed you usually don't have -to. +many things are preconfigured. For example, by convention templates and +static files are stored in subdirectories within the application's Python source tree. +While this can be changed you usually don't have to. -The main reason Flask is called a "microframework" is the idea to keep -the core simple but extensible. There is no database abstraction +The main reason Flask is called a "microframework" is the idea +to keep the core simple but extensible. There is no database abstraction layer, no form validation or anything else where different libraries -already exist that can handle that. However Flask supports extensions -to add such functionality to your application as if it was implemented -in Flask itself. There are currently extensions for object-relational -mappers, form validation, upload handling, various open authentication -technologies and more. +already exist that can handle that. However Flask supports +extensions to add such functionality to your application as if it +was implemented in Flask itself. There are currently extensions for +object-relational mappers, form validation, upload handling, various open +authentication technologies and more. -Since Flask is based on a very solid foundation there is not a lot of -code in Flask itself. As such it's easy to adapt even for large -applications and we are making sure that you can either configure it -as much as possible by subclassing things or by forking the entire -codebase. If you are interested in that, check out the -:ref:`becomingbig` chapter. +Since Flask is based on a very solid foundation there is not a lot of code +in Flask itself. As such it's easy to adapt even for large applications +and we are making sure that you can either configure it as much as +possible by subclassing things or by forking the entire codebase. If you +are interested in that, check out the :ref:`becomingbig` chapter. If you are curious about the Flask design principles, head over to the section about :ref:`design`. -Web Development is Dangerous ---------------------------- +Web Development is Dangerous +---------------------------- -If you write a web application, you are probably allowing users to -register and leave their data on your server. The users are -entrusting you with data. And even if you are the only user that -might leave data in your application, you still want that data to be -stored securely. +If you write a web application, you are probably allowing users to register +and leave their data on your server. The users are entrusting you with data. +And even if you are the only user that might leave data in your application, +you still want that data to be stored securely. -Unfortunately, there are many ways the security of a web application -can be compromised. Flask protects you against one of the most common -security problems of modern web applications: cross-site scripting -(XSS). Unless you deliberately mark insecure HTML as secure, Flask -and the underlying Jinja2 template engine have you covered. But there -are many more ways to cause security problems. +Unfortunately, there are many ways the security of a web application can be +compromised. Flask protects you against one of the most common security +problems of modern web applications: cross-site scripting (XSS). Unless +you deliberately mark insecure HTML as secure, Flask and the underlying +Jinja2 template engine have you covered. But there are many more ways to +cause security problems. The documentation will warn you about aspects of web development that -require attention to security. Some of these security concerns are -far more complex than one might think, and we all sometimes -underestimate the likelihood that a vulnerability will be exploited - -until a clever attacker figures out a way to exploit our applications. -And don't think that your application is not important enough to -attract an attacker. Depending on the kind of attack, chances are that -automated bots are probing for ways to fill your database with spam, -links to malicious software, and the like. +require attention to security. Some of these security concerns +are far more complex than one might think, and we all sometimes underestimate +the likelihood that a vulnerability will be exploited - until a clever +attacker figures out a way to exploit our applications. And don't think +that your application is not important enough to attract an attacker. +Depending on the kind of attack, chances are that automated bots are +probing for ways to fill your database with spam, links to malicious +software, and the like. So always keep security in mind when doing web development. -The Status of Python 3 +The Status of Python 3 ---------------------- -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 us 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. +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 us 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. -Werkzeug and Flask will be ported to Python 3 as soon as a solution -for the changes is found, and we will provide helpful tips how to -upgrade existing applications to Python 3. Until then, we strongly -recommend using Python 2.6 and 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 `_. +Werkzeug and Flask will be ported to Python 3 as soon as a solution for +the changes is found, and we will provide helpful tips how to upgrade +existing applications to Python 3. Until then, we strongly recommend +using Python 2.6 and 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 `_. From 756a5565ea395c5113e8e9cf21b39060548aa5ba Mon Sep 17 00:00:00 2001 From: wilsaj Date: Mon, 12 Mar 2012 17:21:49 -0500 Subject: [PATCH 0110/2940] docfix: wrong converter name: unicode -> string --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index ec7e4f63..fe871112 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -511,7 +511,7 @@ Variable parts are passed to the view function as keyword arguments. The following converters are available: =========== =============================================== -`unicode` accepts any text without a slash (the default) +`string` accepts any text without a slash (the default) `int` accepts integers `float` like `int` but for floating point values `path` like the default but also accepts slashes From a77938837c6466edfde7f1708ef56587189a5e2b Mon Sep 17 00:00:00 2001 From: wilsaj Date: Mon, 12 Mar 2012 17:21:49 -0500 Subject: [PATCH 0111/2940] docfix: wrong converter name: unicode -> string fixes #364 --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index ec7e4f63..fe871112 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -511,7 +511,7 @@ Variable parts are passed to the view function as keyword arguments. The following converters are available: =========== =============================================== -`unicode` accepts any text without a slash (the default) +`string` accepts any text without a slash (the default) `int` accepts integers `float` like `int` but for floating point values `path` like the default but also accepts slashes From 8d1546f8e64e093847ad3d5579ad5f9b7c3d0e45 Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Mon, 12 Mar 2012 16:28:39 -0700 Subject: [PATCH 0112/2940] Reword the docs for writing a flask extension There was a minor bug in the example extension that's been fixed. I also updated the description of the fixed code accordingly, and expanded on the usage of _request_ctx_stack.top for adding data that should be accesible to view functions. I verified that the existing code as is works as expected. --- docs/extensiondev.rst | 149 ++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 85 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 074d06ab..ab038e0c 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -125,9 +125,8 @@ Initializing Extensions ----------------------- Many extensions will need some kind of initialization step. For example, -consider your application is currently connecting to SQLite like the -documentation suggests (:ref:`sqlite3`) you will need to provide a few -functions and before / after request handlers. So how does the extension +consider an application that's currently connecting to SQLite like the +documentation suggests (:ref:`sqlite3`). So how does the extension know the name of the application object? Quite simple: you pass it to it. @@ -135,12 +134,14 @@ Quite simple: you pass it to it. There are two recommended ways for an extension to initialize: initialization functions: + If your extension is called `helloworld` you might have a function called ``init_helloworld(app[, extra_args])`` that initializes the extension for that application. It could attach before / after handlers etc. classes: + Classes work mostly like initialization functions but can later be used to further change the behaviour. For an example look at how the `OAuth extension`_ works: there is an `OAuth` object that provides @@ -148,92 +149,18 @@ classes: a remote application that uses OAuth. 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 a -manager object that handles opening and closing database connections. +we will use the class-based approach because it will provide users with an +object that handles opening and closing database connections. The Extension Code ------------------ Here's the contents of the `flask_sqlite3.py` for copy/paste:: - from __future__ import absolute_import import sqlite3 from flask import _request_ctx_stack - class SQLite3(object): - - def __init__(self, app): - self.app = app - self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') - self.app.teardown_request(self.teardown_request) - self.app.before_request(self.before_request) - - def connect(self): - return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) - - def before_request(self): - ctx = _request_ctx_stack.top - ctx.sqlite3_db = self.connect() - - def teardown_request(self, exception): - ctx = _request_ctx_stack.top - ctx.sqlite3_db.close() - - def get_db(self): - ctx = _request_ctx_stack.top - if ctx is not None: - return ctx.sqlite3_db - -So here's what these lines of code do: - -1. The ``__future__`` import is necessary to activate absolute imports. - Otherwise we could not call our module `sqlite3.py` and import the - top-level `sqlite3` module which actually implements the connection to - SQLite. -2. We create a class for our extension that requires a supplied `app` object, - sets a configuration for the database if it's not there - (:meth:`dict.setdefault`), and attaches `before_request` and - `teardown_request` handlers. -3. Next, we define a `connect` function that opens a database connection. -4. Then we set up the request handlers we bound to the app above. Note here - that we're attaching our database connection to the top request context via - `_request_ctx_stack.top`. Extensions should use the top context and not the - `g` object to store things like database connections. -5. Finally, we add a `get_db` function that simplifies access to the context's - database. - -So why did we decide on a class-based approach here? Because using our -extension looks something like this:: - - from flask import Flask - from flask_sqlite3 import SQLite3 - - app = Flask(__name__) - app.config.from_pyfile('the-config.cfg') - manager = SQLite3(app) - db = manager.get_db() - -You can then use the database from views like this:: - - @app.route('/') - def show_all(): - cur = db.cursor() - cur.execute(...) - -Opening a database connection from outside a view function is simple. - ->>> from yourapplication import db ->>> cur = db.cursor() ->>> cur.execute(...) - -Adding an `init_app` Function ------------------------------ - -In practice, you'll almost always want to permit users to initialize your -extension and provide an app object after the fact. This can help avoid -circular import problems when a user is breaking their app into multiple files. -Our extension could add an `init_app` function as follows:: class SQLite3(object): @@ -251,7 +178,7 @@ Our extension could add an `init_app` function as follows:: self.app.before_request(self.before_request) def connect(self): - return sqlite3.connect(app.config['SQLITE3_DATABASE']) + return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) def before_request(self): ctx = _request_ctx_stack.top @@ -261,18 +188,69 @@ Our extension could add an `init_app` function as follows:: ctx = _request_ctx_stack.top ctx.sqlite3_db.close() - def get_db(self): + @property + def connection(self): ctx = _request_ctx_stack.top if ctx is not None: return ctx.sqlite3_db -The user could then initialize the extension in one file:: - manager = SQLite3() +So here's what these lines of code do: -and bind their app to the extension in another file:: +1. The ``__init__`` method takes an optional app object and, if supplied, will + call ``init_app``. +2. The ``init_app`` method exists so that the ``SQLite3`` object can be + instantiated without requiring an app object. This method supports the + factory pattern for creating applications. The ``init_app`` will set the + configuration for the database, defaulting to an in memory database if + no configuration is supplied. In addition, the ``init_app`` method attaches + ``before_request`` and ``teardown_request`` handlers. +3. Next, we define a ``connect`` method that opens a database connection. +4. Then we set up the request handlers we bound to the app above. Note here + that we're attaching our database connection to the top request context via + ``_request_ctx_stack.top``. Extensions should use the top context and not the + ``g`` object to store things like database connections. +5. Finally, we add a ``connection`` property that simplifies access to the context's + database. - manager.init_app(app) +So why did we decide on a class-based approach here? Because using our +extension looks something like this:: + + from flask import Flask + from flask_sqlite3 import SQLite3 + + app = Flask(__name__) + app.config.from_pyfile('the-config.cfg') + db = SQLite3(app) + +You can then use the database from views like this:: + + @app.route('/') + def show_all(): + cur = db.connection.cursor() + cur.execute(...) + +Additionally, the ``init_app`` method is used to support the factory pattern +for creating apps:: + + db = Sqlite3() + # Then later on. + app = create_app('the-config.cfg') + db.init_app(app) + +Keep in mind that supporting this factory pattern for creating apps is required +for approved flask extensions (described below). + + +Using _request_ctx_stack +------------------------ + +In the example above, before every request, a ``sqlite3_db`` variable is assigned +to ``_request_ctx_stack.top``. In a view function, this variable is accessible +using the ``connection`` property of ``SQLite3``. During the teardown of a +request, the ``sqlite3_db`` connection is closed. By using this pattern, the +*same* connection to the sqlite3 database is accessible to anything that needs it +for the duration of the request. End-Of-Request Behavior ----------------------- @@ -292,6 +270,7 @@ pattern is a good way to support both:: else: app.after_request(close_connection) + Strictly speaking the above code is wrong, because teardown functions are passed the exception and typically don't return anything. However because the return value is discarded this will just work assuming that the code From 8f568cfc19f5b5f2aa59b06d4e2b5b8d31423605 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 12 Mar 2012 17:12:55 -0700 Subject: [PATCH 0113/2940] Split foreword into two files; edited lots. --- docs/advanced_foreword.rst | 67 +++++++++++++++++++++ docs/foreword.rst | 118 ++++++++++++------------------------- 2 files changed, 104 insertions(+), 81 deletions(-) create mode 100644 docs/advanced_foreword.rst diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst new file mode 100644 index 00000000..cc1a1843 --- /dev/null +++ b/docs/advanced_foreword.rst @@ -0,0 +1,67 @@ +Foreword for Experienced Programmers +==================================== + +This chapter is for programmers who have worked with other frameworks in the +past, and who may have more specific or esoteric concerns that the typical +user. + +Threads in Flask +---------------- + +One of the design decisions with Flask was that simple tasks should be simple; +they should not take a lot of code and yet they should not limit you. Because +of that we made a few design choices that some people might find surprising or +unorthodox. For example, Flask uses thread-local objects internally so that +you don’t have to pass objects around from function to function within a +request in order to stay threadsafe. While this is a really easy approach and +saves you a lot of time, it might also cause some troubles for very large +applications because changes on these thread-local objects can happen anywhere +in the same thread. In order to solve these problems we don’t hide the thread +locals for you but instead embrace them and provide you with a lot of tools to +make it as pleasant as possible to work with them. + +Web Development is Dangerous +---------------------------- + +If you write a web application, you are probably allowing users to register +and leave their data on your server. The users are entrusting you with data. +And even if you are the only user that might leave data in your application, +you still want that data to be stored securely. + +Unfortunately, there are many ways the security of a web application can be +compromised. Flask protects you against one of the most common security +problems of modern web applications: cross-site scripting (XSS). Unless +you deliberately mark insecure HTML as secure, Flask and the underlying +Jinja2 template engine have you covered. But there are many more ways to +cause security problems. + +The documentation will warn you about aspects of web development that +require attention to security. Some of these security concerns +are far more complex than one might think, and we all sometimes underestimate +the likelihood that a vulnerability will be exploited - until a clever +attacker figures out a way to exploit our applications. And don't think +that your application is not important enough to attract an attacker. +Depending on the kind of attack, chances are that automated bots are +probing for ways to fill your database with spam, links to malicious +software, and the like. + +So always keep security in mind when doing web development. + +The Status of Python 3 +---------------------- + +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 us 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. + +Werkzeug and Flask will be ported to Python 3 as soon as a solution for +the changes is found, and we will provide helpful tips how to upgrade +existing applications to Python 3. Until then, we strongly recommend +using Python 2.6 and 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 `_. diff --git a/docs/foreword.rst b/docs/foreword.rst index 5751ccb7..b186aba6 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -8,92 +8,48 @@ should or should not be using it. What does "micro" mean? ----------------------- -Flask considers the "micro" in microframework to refer not only to the -simplicity and small size of the framework, but also to the fact that it does -not make many decisions for you. While Flask does pick a templating engine -for you, we won't make such decisions for your datastore or other parts. +“Micro” does not mean that your whole web application has to fit into +a single Python file (although it certainly can). Nor does it mean +that Flask is lacking in functionality. The "micro" in microframework +means Flask aims to keep the core simple but extensible. Flask won't make +many decisions for you, such as what database to use. Those decisions that +it does make, such as what templating engine to use, are easy to change. +Everything else is up to you, so that Flask can be everything you need +and nothing you don't. -However, to us the term “micro” does not mean that the whole implementation -has to fit into a single Python file. +By default, Flask does not include a database abstraction layer, form +validation or anything else where different libraries already exist that can +handle that. Instead, FLask extensions add such functionality to your +application as if it was implemented in Flask itself. Numerous extensions +provide database integration, form validation, upload handling, various open +authentication technologies, and more. Flask may be "micro", but the +possibilities are endless. -One of the design decisions with Flask was that simple tasks should be -simple; they should not take a lot of code and yet they should not limit you. -Because of that we made a few design choices that some people might find -surprising or unorthodox. For example, Flask uses thread-local objects -internally so that you don't have to pass objects around from function to -function within a request in order to stay threadsafe. While this is a -really easy approach and saves you a lot of time, it might also cause some -troubles for very large applications because changes on these thread-local -objects can happen anywhere in the same thread. In order to solve these -problems we don't hide the thread locals for you but instead embrace them -and provide you with a lot of tools to make it as pleasant as possible to -work with them. +Convention over Configuration +----------------------------- -Flask is also based on convention over configuration, which means that -many things are preconfigured. For example, by convention templates and -static files are stored in subdirectories within the application's Python source tree. -While this can be changed you usually don't have to. +Flask is based on convention over configuration, which means that many things +are preconfigured. For example, by convention templates and static files are +stored in subdirectories within the application's Python source tree. While +this can be changed you usually don't have to. We want to minimize the time +you need to spend in order to get up and running, without assuming things +about your needs. -The main reason Flask is called a "microframework" is the idea -to keep the core simple but extensible. There is no database abstraction -layer, no form validation or anything else where different libraries -already exist that can handle that. However Flask supports -extensions to add such functionality to your application as if it -was implemented in Flask itself. There are currently extensions for -object-relational mappers, form validation, upload handling, various open -authentication technologies and more. +Growing Up +---------- -Since Flask is based on a very solid foundation there is not a lot of code -in Flask itself. As such it's easy to adapt even for large applications -and we are making sure that you can either configure it as much as -possible by subclassing things or by forking the entire codebase. If you -are interested in that, check out the :ref:`becomingbig` chapter. +Since Flask is based on a very solid foundation there is not a lot of code in +Flask itself. As such it's easy to adapt even for large applications and we +are making sure that you can either configure it as much as possible by +subclassing things or by forking the entire codebase. If you are interested +in that, check out the :ref:`becomingbig` chapter. -If you are curious about the Flask design principles, head over to the -section about :ref:`design`. +If you are curious about the Flask design principles, head over to the section +about :ref:`design`. -Web Development is Dangerous ----------------------------- +For the Stalwart and Wizened... +------------------------------- -If you write a web application, you are probably allowing users to register -and leave their data on your server. The users are entrusting you with data. -And even if you are the only user that might leave data in your application, -you still want that data to be stored securely. - -Unfortunately, there are many ways the security of a web application can be -compromised. Flask protects you against one of the most common security -problems of modern web applications: cross-site scripting (XSS). Unless -you deliberately mark insecure HTML as secure, Flask and the underlying -Jinja2 template engine have you covered. But there are many more ways to -cause security problems. - -The documentation will warn you about aspects of web development that -require attention to security. Some of these security concerns -are far more complex than one might think, and we all sometimes underestimate -the likelihood that a vulnerability will be exploited - until a clever -attacker figures out a way to exploit our applications. And don't think -that your application is not important enough to attract an attacker. -Depending on the kind of attack, chances are that automated bots are -probing for ways to fill your database with spam, links to malicious -software, and the like. - -So always keep security in mind when doing web development. - -The Status of Python 3 ----------------------- - -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 us 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. - -Werkzeug and Flask will be ported to Python 3 as soon as a solution for -the changes is found, and we will provide helpful tips how to upgrade -existing applications to Python 3. Until then, we strongly recommend -using Python 2.6 and 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're more curious about the minutiae of Flask's implementation, and +whether its structure is right for your needs, read the +:ref:`advanced_foreword`. From 3bf1750b5dfde8890eab52850bf2e6c0a3de65cf Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 13 Mar 2012 12:12:47 -0700 Subject: [PATCH 0114/2940] Tighten quickstart deployment docs. --- docs/deploying/index.rst | 3 +++ docs/quickstart.rst | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index d258df89..1b4189c3 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -13,6 +13,9 @@ If you have a different WSGI server look up the server documentation about how to use a WSGI app with it. Just remember that your :class:`Flask` application object is the actual WSGI application. +For hosted options to get up and running quickly, see +:ref:`quickstart_deployment` in the Quickstart. + .. toctree:: :maxdepth: 2 diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8ff7f3cf..0d8c5b73 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -827,18 +827,25 @@ can do it like this:: from werkzeug.contrib.fixers import LighttpdCGIRootFix app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app) +.. _quickstart_deployment: + Deploying to a Web Server ------------------------- -If you want to make your Flask app available to the Internet at large, `Heroku -`_ is very easy to set up and will run small Flask -applications for free. `Check out their tutorial on how to deploy Flask apps on -their service `_. - -There are a number of other websites that will host your Flask app and make it -easy for you to do so. +Ready to deploy your new Flask app? To wrap up the quickstart, you can +immediately deploy to a hosted platform, all of which offer a free plan for +small projects: +- `Deploying Flask on Heroku `_ - `Deploying Flask on ep.io `_ +- `Deploying WSGI on dotCloud `_ + with `Flask-specific notes `_ + +Other places where you can host your Flask app: + - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ - `Sharing your Localhost Server with Localtunnel `_ + +If you manage your own hosts and would like to host yourself, see the chapter +on :ref:`deployment`. From c1a2e3cf1479382c1d1e5c46cd2d1ca669df5889 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 13 Mar 2012 13:41:03 -0700 Subject: [PATCH 0115/2940] Add Rule #0 to extension development. --- docs/extensiondev.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 5a8d5b16..d997b2de 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -326,6 +326,10 @@ 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. From 146088d58066f16ef4bc8172f8120402517c34d3 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 13 Mar 2012 14:37:48 -0700 Subject: [PATCH 0116/2940] Expand docs on send_file option hook, #433. --- CHANGES | 4 ++++ flask/helpers.py | 18 ++++++++++++++++-- flask/testsuite/helpers.py | 4 +--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index dbf447db..6b96be9e 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,10 @@ Relase date to be decided, codename to be chosen. - View functions can now return a tuple with the first instance being an instance of :class:`flask.Response`. This allows for returning ``jsonify(error="error msg"), 400`` from a view function. +- :class:`flask.Flask` now provides a `get_static_file_options` hook for + subclasses to override behavior of serving static files through Flask, + optionally by filename, which for example allows changing cache controls by + file extension. Version 0.8.1 diff --git a/flask/helpers.py b/flask/helpers.py index 4cb918d2..9964792b 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -653,8 +653,22 @@ class _PackageBoundObject(object): self.template_folder)) def get_static_file_options(self, filename): - """Function used internally to determine what keyword arguments - to send to :func:`send_from_directory` for a specific file.""" + """Provides keyword arguments to send to :func:`send_from_directory`. + + This allows subclasses to change the behavior when sending files based + on the filename. For example, to set the cache timeout for .js files + to 60 seconds (note the options are keywords for :func:`send_file`):: + + class MyFlask(flask.Flask): + def get_static_file_options(self, filename): + options = super(MyFlask, self).get_static_file_options(filename) + if filename.lower().endswith('.js'): + options['cache_timeout'] = 60 + options['conditional'] = True + return options + + .. versionaded:: 0.9 + """ return {} def send_static_file(self, filename): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index c88026d9..42331993 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -213,8 +213,6 @@ class SendfileTestCase(FlaskTestCase): self.assert_equal(cc.max_age, 12 * 60 * 60) # override get_static_file_options with some new values and check them class StaticFileApp(flask.Flask): - def __init__(self): - super(StaticFileApp, self).__init__(__name__) def get_static_file_options(self, filename): opts = super(StaticFileApp, self).get_static_file_options(filename) opts['cache_timeout'] = 10 @@ -222,7 +220,7 @@ class SendfileTestCase(FlaskTestCase): # keyword arg in the guts opts['conditional'] = True return opts - app = StaticFileApp() + app = StaticFileApp(__name__) with app.test_request_context(): rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) From d94efc6db63516b7f72e58c34ae33700f3d9c4fb Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 13 Mar 2012 16:34:16 -0700 Subject: [PATCH 0117/2940] Expose send_file max-age as config value, #433. Need to add the same hook in a Blueprint, but this is the first such case where we need app.config in the Blueprint. --- CHANGES | 12 ++++++++---- docs/config.rst | 9 ++++++++- flask/app.py | 7 +++++++ flask/helpers.py | 14 +++++++++----- flask/testsuite/blueprints.py | 14 ++++++++++++++ flask/testsuite/helpers.py | 13 +++++++++---- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index 6b96be9e..ee029adc 100644 --- a/CHANGES +++ b/CHANGES @@ -48,10 +48,14 @@ Relase date to be decided, codename to be chosen. - View functions can now return a tuple with the first instance being an instance of :class:`flask.Response`. This allows for returning ``jsonify(error="error msg"), 400`` from a view function. -- :class:`flask.Flask` now provides a `get_static_file_options` hook for - subclasses to override behavior of serving static files through Flask, - optionally by filename, which for example allows changing cache controls by - file extension. +- :class:`flask.Flask` now provides a `get_send_file_options` hook for + subclasses to override behavior of serving static files from Flask when using + :meth:`flask.Flask.send_static_file` based on keywords in + :func:`flask.helpers.send_file`. This hook is provided a filename, which for + example allows changing cache controls by file extension. The default + max-age for `send_static_file` can be configured through a new + ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether + the `get_send_file_options` hook is used. Version 0.8.1 diff --git a/docs/config.rst b/docs/config.rst index 2f9d8307..cf3c6a4a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -107,6 +107,13 @@ The following configuration values are used internally by Flask: reject incoming requests with a content length greater than this by returning a 413 status code. +``SEND_FILE_MAX_AGE_DEFAULT``: Default cache control max age to use with + :meth:`flask.Flask.send_static_file`, in + seconds. Override this value on a per-file + basis using the + :meth:`flask.Flask.get_send_file_options` and + :meth:`flask.Blueprint.get_send_file_options` + hooks. Defaults to 43200 (12 hours). ``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will not execute the error handlers of HTTP exceptions but instead treat the @@ -267,7 +274,7 @@ configuration:: class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' - + class DevelopmentConfig(Config): DEBUG = True diff --git a/flask/app.py b/flask/app.py index f3d7efcb..0876ac64 100644 --- a/flask/app.py +++ b/flask/app.py @@ -249,6 +249,7 @@ class Flask(_PackageBoundObject): 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'MAX_CONTENT_LENGTH': None, + 'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False }) @@ -1020,6 +1021,12 @@ class Flask(_PackageBoundObject): self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \ .append((code_or_exception, f)) + def get_send_file_options(self, filename): + # Override: Hooks in SEND_FILE_MAX_AGE_DEFAULT config. + options = super(Flask, self).get_send_file_options(filename) + options['cache_timeout'] = self.config['SEND_FILE_MAX_AGE_DEFAULT'] + return options + @setupmethod def template_filter(self, name=None): """A decorator that is used to register custom template filter. diff --git a/flask/helpers.py b/flask/helpers.py index 9964792b..52b0cebc 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -319,6 +319,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, guessing requires a `filename` or an `attachment_filename` to be provided. + Note `get_send_file_options` in :class:`flask.Flask` hooks the + ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default + cache_timeout. + Please never pass filenames to this function from user sources without checking them first. Something like this is usually sufficient to avoid security problems:: @@ -652,7 +656,7 @@ class _PackageBoundObject(object): return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) - def get_static_file_options(self, filename): + def get_send_file_options(self, filename): """Provides keyword arguments to send to :func:`send_from_directory`. This allows subclasses to change the behavior when sending files based @@ -660,14 +664,14 @@ class _PackageBoundObject(object): to 60 seconds (note the options are keywords for :func:`send_file`):: class MyFlask(flask.Flask): - def get_static_file_options(self, filename): - options = super(MyFlask, self).get_static_file_options(filename) + def get_send_file_options(self, filename): + options = super(MyFlask, self).get_send_file_options(filename) if filename.lower().endswith('.js'): options['cache_timeout'] = 60 options['conditional'] = True return options - .. versionaded:: 0.9 + .. versionadded:: 0.9 """ return {} @@ -680,7 +684,7 @@ class _PackageBoundObject(object): if not self.has_static_folder: raise RuntimeError('No static folder for this object') return send_from_directory(self.static_folder, filename, - **self.get_static_file_options(filename)) + **self.get_send_file_options(filename)) def open_resource(self, resource, mode='rb'): """Opens a resource from the application's resource folder. To see diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 5bf81d92..5f3d3ab3 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -16,6 +16,7 @@ import unittest import warnings from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning from werkzeug.exceptions import NotFound +from werkzeug.http import parse_cache_control_header from jinja2 import TemplateNotFound @@ -357,6 +358,19 @@ class BlueprintTestCase(FlaskTestCase): rv = c.get('/admin/static/css/test.css') self.assert_equal(rv.data.strip(), '/* nested file */') + # try/finally, in case other tests use this app for Blueprint tests. + max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT'] + try: + expected_max_age = 3600 + if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age: + expected_max_age = 7200 + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age + rv = c.get('/admin/static/css/test.css') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, expected_max_age) + finally: + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default + with app.test_request_context(): self.assert_equal(flask.url_for('admin.static', filename='test.txt'), '/admin/static/test.txt') diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 42331993..b4dd00ea 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -206,15 +206,20 @@ class SendfileTestCase(FlaskTestCase): def test_static_file(self): app = flask.Flask(__name__) - # default cache timeout is 12 hours (hard-coded) + # default cache timeout is 12 hours with app.test_request_context(): rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 12 * 60 * 60) - # override get_static_file_options with some new values and check them + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600 + with app.test_request_context(): + rv = app.send_static_file('index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 3600) + # override get_send_file_options with some new values and check them class StaticFileApp(flask.Flask): - def get_static_file_options(self, filename): - opts = super(StaticFileApp, self).get_static_file_options(filename) + def get_send_file_options(self, filename): + opts = super(StaticFileApp, self).get_send_file_options(filename) opts['cache_timeout'] = 10 # this test catches explicit inclusion of the conditional # keyword arg in the guts From 73cb15ed2cb208381b31e7f868adfb4117cc803d Mon Sep 17 00:00:00 2001 From: iammookli Date: Thu, 15 Mar 2012 18:20:17 -0700 Subject: [PATCH 0118/2940] Update docs/patterns/mongokit.rst --- docs/patterns/mongokit.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/mongokit.rst b/docs/patterns/mongokit.rst index a9c4eef5..b50cf456 100644 --- a/docs/patterns/mongokit.rst +++ b/docs/patterns/mongokit.rst @@ -141,4 +141,4 @@ These results are also dict-like objects: u'admin@localhost' For more information about MongoKit, head over to the -`website `_. +`website `_. From fe9f5a47687cccaaaf13f160d747ce8b4c03bad9 Mon Sep 17 00:00:00 2001 From: jtsoi Date: Fri, 16 Mar 2012 09:38:40 +0100 Subject: [PATCH 0119/2940] Added an example of how to configure debugging with run_simple, it has to be enabled both for the Flask app and the Werkzeug server. --- docs/patterns/appdispatch.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index 177ade2b..c48d3c28 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -30,6 +30,23 @@ at :func:`werkzeug.serving.run_simple`:: Note that :func:`run_simple ` is not intended for use in production. Use a :ref:`full-blown WSGI server `. +In order to use the interactive debuggger, debugging must be enables both on +the application and the simple server, here is the "hello world" example with debugging and +:func:`run_simple ` : + + from flask import Flask + from werkzeug.serving import run_simple + + app = Flask(__name__) + app.debug = True + + @app.route('/') + def hello_world(): + return 'Hello World!' + + if __name__ == '__main__': + run_simple('localhost', 5000, app, use_reloader=True, use_debugger=True, use_evalex=True) + Combining Applications ---------------------- From ee6ed491d3a783076c278e4b4390baf14e6f3321 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 19 Mar 2012 00:52:33 +0100 Subject: [PATCH 0120/2940] Have tox install simplejson for python 2.5 --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 91c6d664..82c1588e 100644 --- a/tox.ini +++ b/tox.ini @@ -3,3 +3,6 @@ envlist=py25,py26,py27,pypy [testenv] commands=python run-tests.py + +[testenv:py25] +deps=simplejson From bb99158c870a2d761f1349af02ef1decf0d10c7b Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Mon, 19 Mar 2012 22:33:43 -0700 Subject: [PATCH 0121/2940] Remove an unused iteration variable. We can just iterate over the namespace dictionary's keys here. We don't need its values. --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 79d62992..5192c1c1 100644 --- a/flask/views.py +++ b/flask/views.py @@ -107,7 +107,7 @@ class MethodViewType(type): rv = type.__new__(cls, name, bases, d) if 'methods' not in d: methods = set(rv.methods or []) - for key, value in d.iteritems(): + for key in d: if key in http_method_funcs: methods.add(key.upper()) # if we have no method at all in there we don't want to From c2661dd4bcb41e5a4c47709a8be7704870aba0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Risti=C4=87?= Date: Tue, 20 Mar 2012 22:07:58 +0100 Subject: [PATCH 0122/2940] Update docs/patterns/packages.rst --- docs/patterns/packages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 79fd2c58..1c7f9bd0 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -55,7 +55,7 @@ following quick checklist: `__init__.py` file. That way each module can import it safely and the `__name__` variable will resolve to the correct package. 2. all the view functions (the ones with a :meth:`~flask.Flask.route` - decorator on top) have to be imported when in the `__init__.py` file. + decorator on top) have to be imported in the `__init__.py` file. Not the object itself, but the module it is in. Import the view module **after the application object is created**. From b29834dac37a13f82019aa1c7e9d06622cf5790e Mon Sep 17 00:00:00 2001 From: Kamil Kisiel Date: Sat, 24 Mar 2012 14:09:43 -0700 Subject: [PATCH 0123/2940] Fixed weird string quoting in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 812b2c8a..8169a517 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ class run_audit(Command): try: import pyflakes.scripts.pyflakes as flakes except ImportError: - print "Audit requires PyFlakes installed in your system.""" + print "Audit requires PyFlakes installed in your system." sys.exit(-1) warns = 0 From e08028de5521caf41fc11de8daec2795f1f51088 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 27 Mar 2012 17:08:55 +0300 Subject: [PATCH 0124/2940] pip vs. easy_install consistency --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8e6a4497..791c99f1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -94,7 +94,7 @@ System-Wide Installation ------------------------ This is possible as well, though I do not recommend it. Just run -`easy_install` with root privileges:: +`pip` with root privileges:: $ sudo pip install Flask From 35383ee83c568ce642ffef6733d8b91ebd206185 Mon Sep 17 00:00:00 2001 From: Pascal Hartig Date: Wed, 28 Mar 2012 10:33:27 +0300 Subject: [PATCH 0125/2940] Removed triple-quotes from print statement in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 812b2c8a..8169a517 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ class run_audit(Command): try: import pyflakes.scripts.pyflakes as flakes except ImportError: - print "Audit requires PyFlakes installed in your system.""" + print "Audit requires PyFlakes installed in your system." sys.exit(-1) warns = 0 From e76db15e26b76bdaed4649474f6509e383142e9c Mon Sep 17 00:00:00 2001 From: Sergey Date: Sat, 31 Mar 2012 10:11:12 +0300 Subject: [PATCH 0126/2940] Update docs/quickstart.rst --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 0d8c5b73..8f38aff5 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -193,7 +193,7 @@ The following converters exist: with a trailing slash will produce a 404 "Not Found" error. This behavior allows relative URLs to continue working if users access the - page when they forget a trailing slash, consistent with how with how Apache + page when they forget a trailing slash, consistent with how Apache and other servers work. Also, the URLs will stay unique, which helps search engines avoid indexing the same page twice. From 7e4b705b3c7124bc5bdd4051705488d8bb31eb5b Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 1 Apr 2012 10:54:00 -0400 Subject: [PATCH 0127/2940] Move others.rst to wsgi-standalone.rst. --- docs/deploying/{others.rst => wsgi-standalone.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/deploying/{others.rst => wsgi-standalone.rst} (100%) diff --git a/docs/deploying/others.rst b/docs/deploying/wsgi-standalone.rst similarity index 100% rename from docs/deploying/others.rst rename to docs/deploying/wsgi-standalone.rst From 976c9576bd7b41f955862448c9914774dc47d1cf Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 1 Apr 2012 10:54:27 -0400 Subject: [PATCH 0128/2940] Reorder deployment options. --- docs/deploying/fastcgi.rst | 11 +++---- docs/deploying/index.rst | 6 ++-- docs/deploying/uwsgi.rst | 9 +++-- docs/deploying/wsgi-standalone.rst | 53 ++++++++++++++++-------------- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index 6dace1a8..ebd68560 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -3,12 +3,11 @@ FastCGI ======= -FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, -and `cherokee`_; see :ref:`deploying-uwsgi` and -:ref:`deploying-other-servers` for other options. To use your WSGI -application with any of them you will need a FastCGI server first. The -most popular one is `flup`_ which we will use for this guide. Make sure -to have it installed to follow along. +FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, and +`cherokee`_; see :ref:`deploying-uwsgi` and :ref:`deploying-wsgi-standalone` +for other options. To use your WSGI application with any of them you will need +a FastCGI server first. The most popular one is `flup`_ which we will use for +this guide. Make sure to have it installed to follow along. .. admonition:: Watch Out diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 1b4189c3..bf78275d 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -20,7 +20,7 @@ For hosted options to get up and running quickly, see :maxdepth: 2 mod_wsgi - cgi - fastcgi + wsgi-standalone uwsgi - others + fastcgi + cgi diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index bdee15ba..b05fdeec 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -4,11 +4,10 @@ uWSGI ===== uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and -`cherokee`_; see :ref:`deploying-fastcgi` and -:ref:`deploying-other-servers` for other options. To use your WSGI -application with uWSGI protocol you will need a uWSGI server -first. uWSGI is both a protocol and an application server; the -application server can serve uWSGI, FastCGI, and HTTP protocols. +`cherokee`_; see :ref:`deploying-fastcgi` and :ref:`deploying-wsgi-standalone` +for other options. To use your WSGI application with uWSGI protocol you will +need a uWSGI server first. uWSGI is both a protocol and an application server; +the application server can serve uWSGI, FastCGI, and HTTP protocols. The most popular uWSGI server is `uwsgi`_, which we will use for this guide. Make sure to have it installed to follow along. diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 6f3e5cc6..4bb985d4 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -1,11 +1,31 @@ -.. _deploying-other-servers: +.. _deploying-wsgi-standalone: -Other Servers -============= +Standalone WSGI Containers +========================== -There are popular servers written in Python that allow the execution of WSGI -applications as well. These servers stand alone when they run; you can proxy -to them from your web server. +There are popular servers written in Python that contain WSGI applications and +serve HTTP. These servers stand alone when they run; you can proxy to them +from your web server. Note the section on :ref:`deploying-proxy-setups` if you +run into issues. + +Gunicorn +-------- + +`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork +worker model ported from Ruby's Unicorn project. It supports both `eventlet`_ +and `greenlet`_. Running a Flask application on this server is quite simple:: + + gunicorn myproject:app + +`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``. +For example, to run a Flask application with 4 worker processes (``-w +4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``):: + + gunicorn -w 4 -b 127.0.0.1:4000 myproject:app + +.. _Gunicorn: http://gunicorn.org/ +.. _eventlet: http://eventlet.net/ +.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html Tornado -------- @@ -14,7 +34,7 @@ Tornado server and tools that power `FriendFeed`_. Because it is non-blocking and uses epoll, it can handle thousands of simultaneous standing connections, which means it is ideal for real-time web services. Integrating this -service with Flask is a trivial task:: +service with Flask is straightforward:: from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer @@ -46,24 +66,7 @@ event loop:: .. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html .. _libevent: http://monkey.org/~provos/libevent/ -Gunicorn --------- - -`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork -worker model ported from Ruby's Unicorn project. It supports both `eventlet`_ -and `greenlet`_. Running a Flask application on this server is quite simple:: - - gunicorn myproject:app - -`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``. -For example, to run a Flask application with 4 worker processes (``-w -4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``):: - - gunicorn -w 4 -b 127.0.0.1:4000 myproject:app - -.. _Gunicorn: http://gunicorn.org/ -.. _eventlet: http://eventlet.net/ -.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html +.. _deploying-proxy-setups: Proxy Setups ------------ From 9a1d616706d251b19571d908282deadedd89869b Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 1 Apr 2012 10:57:44 -0400 Subject: [PATCH 0129/2940] Add simple proxying nginx config to docs. Origin: https://gist.github.com/2269917 --- docs/deploying/wsgi-standalone.rst | 34 ++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 4bb985d4..422a9340 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -71,12 +71,34 @@ event loop:: Proxy Setups ------------ -If you deploy your application using one of these servers behind an HTTP -proxy you will need to rewrite a few headers in order for the -application to work. The two problematic values in the WSGI environment -usually are `REMOTE_ADDR` and `HTTP_HOST`. Werkzeug ships a fixer that -will solve some common setups, but you might want to write your own WSGI -middleware for specific setups. +If you deploy your application using one of these servers behind an HTTP proxy +you will need to rewrite a few headers in order for the application to work. +The two problematic values in the WSGI environment usually are `REMOTE_ADDR` +and `HTTP_HOST`. You can configure your httpd to pass these headers, or you +can fix them in middleware. Werkzeug ships a fixer that will solve some common +setups, but you might want to write your own WSGI middleware for specific +setups. + +Here's a simple nginx configuration which proxies to an application served on +localhost at port 8000, setting appropriate headers:: + + server { + listen 80; + + server_name _; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + location / { + proxy_pass http://127.0.0.1:8000/; + proxy_redirect off; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } The most common setup invokes the host being set from `X-Forwarded-Host` and the remote address from `X-Forwarded-For`:: From 9ab41edbd727d69d6936b866ea606c9b7e7bac8f Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 1 Apr 2012 11:19:51 -0400 Subject: [PATCH 0130/2940] Touch up proxying docs. --- docs/deploying/wsgi-standalone.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 422a9340..74385813 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -80,7 +80,9 @@ setups, but you might want to write your own WSGI middleware for specific setups. Here's a simple nginx configuration which proxies to an application served on -localhost at port 8000, setting appropriate headers:: +localhost at port 8000, setting appropriate headers: + +.. sourcecode:: nginx server { listen 80; @@ -100,15 +102,18 @@ localhost at port 8000, setting appropriate headers:: } } -The most common setup invokes the host being set from `X-Forwarded-Host` -and the remote address from `X-Forwarded-For`:: +If your httpd is not providing these headers, the most common setup invokes the +host being set from `X-Forwarded-Host` and the remote address from +`X-Forwarded-For`:: from werkzeug.contrib.fixers import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app) -Please keep in mind that it is a security issue to use such a middleware -in a non-proxy setup because it will blindly trust the incoming -headers which might be forged by malicious clients. +.. admonition:: Trusting Headers + + Please keep in mind that it is a security issue to use such a middleware in + a non-proxy setup because it will blindly trust the incoming headers which + might be forged by malicious clients. If you want to rewrite the headers from another header, you might want to use a fixer like this:: From 0eb75b317bd62ece31875158fb31262ce7d05e69 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 1 Apr 2012 11:33:42 -0400 Subject: [PATCH 0131/2940] Add notes on mutable values & sessions. Using notes in 8445f0d939dc3c4a2e722dc6dd4938d02bc2e094 --- CHANGES | 3 ++- flask/helpers.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ee029adc..bee0ab77 100644 --- a/CHANGES +++ b/CHANGES @@ -56,7 +56,8 @@ Relase date to be decided, codename to be chosen. max-age for `send_static_file` can be configured through a new ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether the `get_send_file_options` hook is used. - +- Fixed an assumption in sessions implementation which could break message + flashing on sessions implementations which use external storage. Version 0.8.1 ------------- diff --git a/flask/helpers.py b/flask/helpers.py index e6fb4ae3..294c5297 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -283,6 +283,13 @@ def flash(message, category='message'): messages and ``'warning'`` for warnings. However any kind of string can be used as category. """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # are always in sync with the sess on object, which is not true for session + # implementations that use external storage for keeping their keys/values. flashes = session.get('_flashes', []) flashes.append((category, message)) session['_flashes'] = flashes From df772df24f22f7f0681a8d5b211dad764ce9c8a6 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 1 Apr 2012 11:40:37 -0400 Subject: [PATCH 0132/2940] Touch up run_simple doc, #446. --- docs/patterns/appdispatch.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index c48d3c28..a2d1176f 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -30,9 +30,9 @@ at :func:`werkzeug.serving.run_simple`:: Note that :func:`run_simple ` is not intended for use in production. Use a :ref:`full-blown WSGI server `. -In order to use the interactive debuggger, debugging must be enables both on -the application and the simple server, here is the "hello world" example with debugging and -:func:`run_simple ` : +In order to use the interactive debuggger, debugging must be enabled both on +the application and the simple server, here is the "hello world" example with +debugging and :func:`run_simple `:: from flask import Flask from werkzeug.serving import run_simple @@ -45,7 +45,8 @@ the application and the simple server, here is the "hello world" example with de return 'Hello World!' if __name__ == '__main__': - run_simple('localhost', 5000, app, use_reloader=True, use_debugger=True, use_evalex=True) + run_simple('localhost', 5000, app, + use_reloader=True, use_debugger=True, use_evalex=True) Combining Applications From 91efb90fb3d580bad438c353bdfe4d604051a3a4 Mon Sep 17 00:00:00 2001 From: jfinkels Date: Sun, 1 Apr 2012 13:03:22 -0300 Subject: [PATCH 0133/2940] Removed extra blank line to fix ReST formatted documentation string in wrappers.py. Should have gone with pull request #439. --- flask/wrappers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index 541d26ef..3ee718ff 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -118,7 +118,6 @@ class Request(RequestBase): this server could not understand."} .. versionchanged:: 0.9 - Return a :class:`JSONBadRequest` instead of a :class:`~werkzeug.exceptions.BadRequest` by default. From f46f7155b27a741081fc13fa7fb1db53e54f5683 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 1 Apr 2012 20:57:50 -0400 Subject: [PATCH 0134/2940] 2012 --- LICENSE | 2 +- docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 5d269389..dc01ee1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS +Copyright (c) 2012 by Armin Ronacher and contributors. See AUTHORS for more details. Some rights reserved. diff --git a/docs/conf.py b/docs/conf.py index 16d7e670..30df7147 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,7 +43,7 @@ master_doc = 'index' # General information about the project. project = u'Flask' -copyright = u'2010, Armin Ronacher' +copyright = u'2012, Armin Ronacher' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From b16c988f1e7de5f0ec9dae11817b9109de59355d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 3 Apr 2012 20:55:58 -0400 Subject: [PATCH 0135/2940] Fix static endpoint name mention in quickstart. --- docs/quickstart.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8f38aff5..8497f082 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -333,8 +333,7 @@ configured to serve them for you, but during development Flask can do that as well. Just create a folder called `static` in your package or next to your module and it will be available at `/static` on the application. -To generate URLs that part of the URL, use the special ``'static'`` URL -name:: +To generate URLs for static files, use the special ``'static'`` endpoint name:: url_for('static', filename='style.css') From 492ef06bff569d6037fa43e561b203fe444b60d5 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 4 Apr 2012 11:39:07 -0400 Subject: [PATCH 0136/2940] Clarify use of context-locals with signals. --- docs/signals.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/signals.rst b/docs/signals.rst index 2d3878f7..df92dce7 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -131,6 +131,8 @@ debugging. You can access the name of the signal with the missing blinker installations, you can do so by using the :class:`flask.signals.Namespace` class. +.. _signals-sending: + Sending Signals --------------- @@ -156,6 +158,17 @@ function, you can pass ``current_app._get_current_object()`` as sender. that :data:`~flask.current_app` is a proxy and not the real application object. + +Signals and Flask's Request Context +----------------------------------- + +Signals fully support :ref:`reqcontext` when receiving signals. Context-local +variables are consistently available between :data:`~flask.request_started` and +:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others +as needed. Note the limitations described in :ref:`signals-sending` and the +:data:`~flask.request_tearing_down` signal. + + Decorator Based Signal Subscriptions ------------------------------------ From f07199009c463ed5eaab7b2cacd785d46f87699d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 4 Apr 2012 11:41:40 -0400 Subject: [PATCH 0137/2940] Fix reqcontext ref in signals doc. --- docs/signals.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/signals.rst b/docs/signals.rst index df92dce7..a4cd4157 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -162,11 +162,11 @@ function, you can pass ``current_app._get_current_object()`` as sender. Signals and Flask's Request Context ----------------------------------- -Signals fully support :ref:`reqcontext` when receiving signals. Context-local -variables are consistently available between :data:`~flask.request_started` and -:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others -as needed. Note the limitations described in :ref:`signals-sending` and the -:data:`~flask.request_tearing_down` signal. +Signals fully support :ref:`request-context` when receiving signals. +Context-local variables are consistently available between +:data:`~flask.request_started` and :data:`~flask.request_finished`, so you can +rely on :class:`flask.g` and others as needed. Note the limitations described +in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal. Decorator Based Signal Subscriptions From 9c48387072128c32dde06dc9a6e812195f18012d Mon Sep 17 00:00:00 2001 From: Dmitry Shevchenko Date: Sun, 8 Apr 2012 18:09:12 -0500 Subject: [PATCH 0138/2940] Removed unneeded print statements form mongokit pattern doc --- docs/patterns/mongokit.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/patterns/mongokit.rst b/docs/patterns/mongokit.rst index b50cf456..b4b6fc01 100644 --- a/docs/patterns/mongokit.rst +++ b/docs/patterns/mongokit.rst @@ -122,9 +122,6 @@ collection first, this is somewhat the same as a table in the SQL world. >>> user = {'name': u'admin', 'email': u'admin@localhost'} >>> collection.insert(user) -print list(collection.find()) -print collection.find_one({'name': u'admin'}) - MongoKit will automatically commit for us. To query your database, you use the collection directly: From a1305973bfef18a341224ad4f748c35d77a64cdb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 14:19:13 +0100 Subject: [PATCH 0139/2940] Fixed a typo in a comment --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 47ac0cc1..f9558d2e 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -95,7 +95,7 @@ class RequestContext(object): self.match_request() - # XXX: Support for deprecated functionality. This is doing away with + # XXX: Support for deprecated functionality. This is going away with # Flask 1.0 blueprint = self.request.blueprint if blueprint is not None: From 47288231fe8f9c6b2c413d50160c32c3884d5785 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 14:34:12 +0100 Subject: [PATCH 0140/2940] Implemented a separate application context. --- flask/app.py | 17 ++++++++++++++- flask/ctx.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++- flask/globals.py | 10 ++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 15e432de..38d31f95 100644 --- a/flask/app.py +++ b/flask/app.py @@ -28,7 +28,7 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ find_package from .wrappers import Request, Response from .config import ConfigAttribute, Config -from .ctx import RequestContext +from .ctx import RequestContext, AppContext from .globals import _request_ctx_stack, request from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module @@ -1458,6 +1458,21 @@ class Flask(_PackageBoundObject): return rv request_tearing_down.send(self) + def app_context(self): + """Binds the application only. For as long as the application is bound + to the current context the :data:`flask.current_app` points to that + application. An application context is automatically created when a + request context is pushed if necessary. + + Example usage:: + + with app.app_context(): + ... + + .. versionadded:: 0.9 + """ + return AppContext(self) + def request_context(self, environ): """Creates a :class:`~flask.ctx.RequestContext` from the given environment and binds it to the current context. This must be used in diff --git a/flask/ctx.py b/flask/ctx.py index f9558d2e..7bfd598e 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -11,7 +11,7 @@ from werkzeug.exceptions import HTTPException -from .globals import _request_ctx_stack +from .globals import _request_ctx_stack, _app_ctx_stack from .module import blueprint_is_module @@ -19,6 +19,14 @@ class _RequestGlobals(object): pass +def _push_app_if_necessary(app): + top = _app_ctx_stack.top + if top is None or top.app != app: + ctx = app.app_context() + ctx.push() + return ctx + + def has_request_context(): """If you have code that wants to test if a request context is there or not this function can be used. For instance, you may want to take advantage @@ -51,6 +59,36 @@ def has_request_context(): return _request_ctx_stack.top is not None +class AppContext(object): + """The application context binds an application object implicitly + to the current thread or greenlet, similar to how the + :class:`RequestContext` binds request information. The application + context is also implicitly created if a request context is created + but the application is not on top of the individual application + context. + """ + + def __init__(self, app): + self.app = app + + def push(self): + """Binds the app context to the current context.""" + _app_ctx_stack.push(self) + + def pop(self): + """Pops the app context.""" + rv = _app_ctx_stack.pop() + assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ + % (rv, self) + + def __enter__(self): + self.push() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.pop() + + class RequestContext(object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the @@ -93,6 +131,11 @@ class RequestContext(object): # is pushed the preserved context is popped. self.preserved = False + # Indicates if pushing this request context also triggered the pushing + # of an application context. If it implicitly pushed an application + # context, it will be stored there + self._pushed_application_context = None + self.match_request() # XXX: Support for deprecated functionality. This is going away with @@ -130,6 +173,10 @@ class RequestContext(object): if top is not None and top.preserved: top.pop() + # Before we push the request context we have to ensure that there + # is an application context. + self._pushed_application_context = _push_app_if_necessary(self.app) + _request_ctx_stack.push(self) # Open the session at the moment that the request context is @@ -154,6 +201,11 @@ class RequestContext(object): # so that we don't require the GC to be active. rv.request.environ['werkzeug.request'] = None + # Get rid of the app as well if necessary. + if self._pushed_application_context: + self._pushed_application_context.pop() + self._pushed_application_context = None + def __enter__(self): self.push() return self diff --git a/flask/globals.py b/flask/globals.py index 16580d16..f6d62485 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -20,9 +20,17 @@ def _lookup_object(name): return getattr(top, name) +def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError('working outside of application context') + return top.app + + # context locals _request_ctx_stack = LocalStack() -current_app = LocalProxy(partial(_lookup_object, 'app')) +_app_ctx_stack = LocalStack() +current_app = LocalProxy(_find_app) request = LocalProxy(partial(_lookup_object, 'request')) session = LocalProxy(partial(_lookup_object, 'session')) g = LocalProxy(partial(_lookup_object, 'g')) From 307d1bc4e51fc35282f2d504b9df3359604b8f8a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:04:35 +0100 Subject: [PATCH 0141/2940] Added support for basic URL generation without request contexts. --- flask/app.py | 19 ++++++++++++--- flask/ctx.py | 1 + flask/helpers.py | 50 +++++++++++++++++++++++++-------------- flask/testsuite/appctx.py | 38 +++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 flask/testsuite/appctx.py diff --git a/flask/app.py b/flask/app.py index 38d31f95..215bf0ad 100644 --- a/flask/app.py +++ b/flask/app.py @@ -250,7 +250,8 @@ class Flask(_PackageBoundObject): 'SESSION_COOKIE_SECURE': False, 'MAX_CONTENT_LENGTH': None, 'TRAP_BAD_REQUEST_ERRORS': False, - 'TRAP_HTTP_EXCEPTIONS': False + 'TRAP_HTTP_EXCEPTIONS': False, + 'PREFERRED_URL_SCHEME': 'http' }) #: The rule object to use for URL rules created. This is used by @@ -1370,9 +1371,21 @@ class Flask(_PackageBoundObject): so the request is passed explicitly. .. versionadded:: 0.6 + + .. versionchanged:: 0.9 + This can now also be called without a request object when the + UR adapter is created for the application context. """ - return self.url_map.bind_to_environ(request.environ, - server_name=self.config['SERVER_NAME']) + if request is not None: + return self.url_map.bind_to_environ(request.environ, + server_name=self.config['SERVER_NAME']) + # We need at the very least the server name to be set for this + # to work. + if self.config['SERVER_NAME'] is not None: + return self.url_map.bind( + self.config['SERVER_NAME'], + script_name=self.config['APPLICATION_ROOT'] or '/', + url_scheme=self.config['PREFERRED_URL_SCHEME']) def inject_url_defaults(self, endpoint, values): """Injects the URL defaults for the given endpoint directly into diff --git a/flask/ctx.py b/flask/ctx.py index 7bfd598e..a9088cf4 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -70,6 +70,7 @@ class AppContext(object): def __init__(self, app): self.app = app + self.url_adapter = app.create_url_adapter(None) def push(self): """Binds the app context to the current context.""" diff --git a/flask/helpers.py b/flask/helpers.py index 25250d26..26be5e30 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -50,7 +50,8 @@ except ImportError: from jinja2 import FileSystemLoader -from .globals import session, _request_ctx_stack, current_app, request +from .globals import session, _request_ctx_stack, _app_ctx_stack, \ + current_app, request def _assert_have_json(): @@ -197,27 +198,40 @@ def url_for(endpoint, **values): :param _anchor: if provided this is added as anchor to the URL. :param _method: if provided this explicitly specifies an HTTP method. """ - ctx = _request_ctx_stack.top - blueprint_name = request.blueprint - if not ctx.request._is_old_module: - if endpoint[:1] == '.': - if blueprint_name is not None: - endpoint = blueprint_name + endpoint - else: + appctx = _app_ctx_stack.top + reqctx = _request_ctx_stack.top + + # If request specific information is available we have some extra + # features that support "relative" urls. + if reqctx is not None: + url_adapter = reqctx.url_adapter + blueprint_name = request.blueprint + if not reqctx.request._is_old_module: + if endpoint[:1] == '.': + if blueprint_name is not None: + endpoint = blueprint_name + endpoint + else: + endpoint = endpoint[1:] + else: + # TODO: get rid of this deprecated functionality in 1.0 + if '.' not in endpoint: + if blueprint_name is not None: + endpoint = blueprint_name + '.' + endpoint + elif endpoint.startswith('.'): endpoint = endpoint[1:] + external = values.pop('_external', False) + + # Otherwise go with the url adapter from the appctx and make + # the urls external by default. else: - # TODO: get rid of this deprecated functionality in 1.0 - if '.' not in endpoint: - if blueprint_name is not None: - endpoint = blueprint_name + '.' + endpoint - elif endpoint.startswith('.'): - endpoint = endpoint[1:] - external = values.pop('_external', False) + url_adapter = appctx.url_adapter + external = values.pop('_external', True) + anchor = values.pop('_anchor', None) method = values.pop('_method', None) - ctx.app.inject_url_defaults(endpoint, values) - rv = ctx.url_adapter.build(endpoint, values, method=method, - force_external=external) + appctx.app.inject_url_defaults(endpoint, values) + rv = url_adapter.build(endpoint, values, method=method, + force_external=external) if anchor is not None: rv += '#' + url_quote(anchor) return rv diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py new file mode 100644 index 00000000..2c198047 --- /dev/null +++ b/flask/testsuite/appctx.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.appctx + ~~~~~~~~~~~~~~~~~~~~~~ + + Tests the application context. + + :copyright: (c) 2012 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class AppContextTestCase(FlaskTestCase): + + def test_basic_support(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + app.config['PREFERRED_URL_SCHEME'] = 'https' + + @app.route('/') + def index(): + pass + + with app.app_context(): + rv = flask.url_for('index') + self.assert_equal(rv, 'https://localhost/') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(AppContextTestCase)) + return suite From f8f2e2dff481e55fa9ae7cc3f70f36d61bcf56d7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:16:09 +0100 Subject: [PATCH 0142/2940] Added more tests for the new stack behavior. --- flask/__init__.py | 3 ++- flask/helpers.py | 8 ++++++++ flask/testsuite/appctx.py | 24 +++++++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index 54bfedda..f35ef328 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -23,7 +23,8 @@ from .config import Config from .helpers import url_for, jsonify, json_available, flash, \ send_file, send_from_directory, get_flashed_messages, \ get_template_attribute, make_response, safe_join -from .globals import current_app, g, request, session, _request_ctx_stack +from .globals import current_app, g, request, session, _request_ctx_stack, \ + _app_ctx_stack from .ctx import has_request_context from .module import Module from .blueprints import Blueprint diff --git a/flask/helpers.py b/flask/helpers.py index 26be5e30..b86ce158 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -200,6 +200,9 @@ def url_for(endpoint, **values): """ appctx = _app_ctx_stack.top reqctx = _request_ctx_stack.top + if appctx is None: + raise RuntimeError('Attempted to generate a URL with the application ' + 'context being pushed. This has to be executed ') # If request specific information is available we have some extra # features that support "relative" urls. @@ -225,6 +228,11 @@ def url_for(endpoint, **values): # the urls external by default. else: url_adapter = appctx.url_adapter + if url_adapter is None: + raise RuntimeError('Application was not able to create a URL ' + 'adapter for request independent URL generation. ' + 'You might be able to fix this by setting ' + 'the SERVER_NAME config variable.') external = values.pop('_external', True) anchor = values.pop('_anchor', None) diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index 2c198047..c60dbc67 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -18,7 +18,7 @@ from flask.testsuite import FlaskTestCase class AppContextTestCase(FlaskTestCase): - def test_basic_support(self): + def test_basic_url_generation(self): app = flask.Flask(__name__) app.config['SERVER_NAME'] = 'localhost' app.config['PREFERRED_URL_SCHEME'] = 'https' @@ -31,6 +31,28 @@ class AppContextTestCase(FlaskTestCase): rv = flask.url_for('index') self.assert_equal(rv, 'https://localhost/') + def test_url_generation_requires_server_name(self): + app = flask.Flask(__name__) + with app.app_context(): + with self.assert_raises(RuntimeError): + flask.url_for('index') + + def test_url_generation_without_context_fails(self): + with self.assert_raises(RuntimeError): + flask.url_for('index') + + def test_request_context_means_app_context(self): + app = flask.Flask(__name__) + with app.test_request_context(): + self.assert_equal(flask.current_app._get_current_object(), app) + self.assert_equal(flask._app_ctx_stack.top, None) + + def test_app_context_provides_current_app(self): + app = flask.Flask(__name__) + with app.app_context(): + self.assert_equal(flask.current_app._get_current_object(), app) + self.assert_equal(flask._app_ctx_stack.top, None) + def suite(): suite = unittest.TestSuite() From 0207e90155abe937568727e4e9eca949b8247cd5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:22:36 +0100 Subject: [PATCH 0143/2940] Updated docs for the app context. --- CHANGES | 3 +++ docs/api.rst | 16 +++++++++++++++- flask/ctx.py | 10 ++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8311e2e7..5436a857 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,9 @@ Relase date to be decided, codename to be chosen. - The :meth:`flask.render_template` method now accepts a either an iterable of template names or a single template name. Previously, it only accepted a single template name. On an iterable, the first template found is rendered. +- Added :meth:`flask.Flask.app_context` which works very similar to the + request context but only provides access to the current application. This + also adds support for URL generation without an active request context. Version 0.8.1 diff --git a/docs/api.rst b/docs/api.rst index ec7e4f63..78ed2d8d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -265,12 +265,16 @@ Useful Functions and Classes Points to the application handling the request. This is useful for extensions that want to support multiple applications running side - by side. + by side. This is powered by the application context and not by the + request context, so you can change the value of this proxy by + using the :meth:`~flask.Flask.app_context` method. This is a proxy. See :ref:`notes-on-proxies` for more information. .. autofunction:: has_request_context +.. autofunction:: has_app_context + .. autofunction:: url_for .. function:: abort(code) @@ -412,6 +416,16 @@ Useful Internals if ctx is not None: return ctx.session +.. autoclass:: flask.ctx.AppContext + :members: + +.. data:: _app_ctx_stack + + Works similar to the request context but only binds the application. + This is mainly there for extensions to store data. + + .. versionadded:: 0.9 + .. autoclass:: flask.blueprints.BlueprintSetupState :members: diff --git a/flask/ctx.py b/flask/ctx.py index a9088cf4..887b2598 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -59,6 +59,16 @@ def has_request_context(): return _request_ctx_stack.top is not None +def has_app_context(): + """Worksl ike :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _app_ctx_stack.top is not None + + class AppContext(object): """The application context binds an application object implicitly to the current thread or greenlet, similar to how the From ab110d8fe573c92f0f607a05cf31549399b98c1a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:24:43 +0100 Subject: [PATCH 0144/2940] Documented config changes --- docs/config.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 2f9d8307..a5d2a9f0 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -95,7 +95,10 @@ The following configuration values are used internally by Flask: ``'myapp.dev:5000'``) Note that localhost does not support subdomains so setting this to “localhost” does not - help. + help. Setting a ``SERVER_NAME`` also + by default enables URL generation + without a request context but with an + application context. ``APPLICATION_ROOT`` If the application does not occupy a whole domain or subdomain this can be set to the path where the application @@ -126,6 +129,9 @@ The following configuration values are used internally by Flask: used to debug those situations. If this config is set to ``True`` you will get a regular traceback instead. +``PREFERRED_URL_SCHEME`` The URL scheme that should be used for + URL generation if no URL scheme is + available. This defaults to ``http``. ================================= ========================================= .. admonition:: More on ``SERVER_NAME`` @@ -165,6 +171,9 @@ The following configuration values are used internally by Flask: ``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``, ``SESSION_COOKIE_SECURE`` +.. versionadded:: 0.9 + ``PREFERRED_URL_SCHEME`` + Configuring from Files ---------------------- From cf1641e5beec1ec11f418fa6e775fc44b9410180 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 15:56:33 +0100 Subject: [PATCH 0145/2940] Changed the implementation of returning tuples from functions --- CHANGES | 2 ++ docs/quickstart.rst | 7 +++-- docs/upgrading.rst | 15 +++++++++++ flask/app.py | 55 ++++++++++++++++++++++++---------------- flask/testsuite/basic.py | 15 +++++------ 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index bb03d088..cb7b751e 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,8 @@ Relase date to be decided, codename to be chosen. the `get_send_file_options` hook is used. - Fixed an assumption in sessions implementation which could break message flashing on sessions implementations which use external storage. +- Changed the behavior of tuple return values from functions. They are no + longer arguments to the response object, they now have a defined meaning. Version 0.8.1 ------------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8497f082..f7b6ee02 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -674,8 +674,11 @@ converting return values into response objects is as follows: returned from the view. 2. If it's a string, a response object is created with that data and the default parameters. -3. If a tuple is returned the response object is created by passing the - tuple as arguments to the response object's constructor. +3. If a tuple is returned the items in the tuple can provide extra + information. Such tuples have to be in the form ``(response, status, + headers)`` where at least one item has to be in the tuple. The + `status` value will override the status code and `headers` can be a + list or dictionary of additional header values. 4. If none of that works, Flask will assume the return value is a valid WSGI application and convert that into a response object. diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 0ba46c13..ab00624e 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -19,6 +19,21 @@ installation, make sure to pass it the ``-U`` parameter:: $ easy_install -U Flask +Version 0.9 +----------- + +The behavior of returning tuples from a function was simplified. If you +return a tuple it no longer defines the arguments for the response object +you're creating, it's now always a tuple in the form ``(response, status, +headers)`` where at least one item has to be provided. If you depend on +the old behavior, you can add it easily by subclassing Flask:: + + class TraditionalFlask(Flask): + def make_response(self, rv): + if isinstance(rv, tuple): + return self.response_class(*rv) + return Flask.make_response(self, rv) + Version 0.8 ----------- diff --git a/flask/app.py b/flask/app.py index e16b35bf..a0ffed66 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1354,37 +1354,48 @@ class Flask(_PackageBoundObject): string as body :class:`unicode` a response object is created with the string encoded to utf-8 as body - :class:`tuple` the response object is created with the - contents of the tuple as arguments a WSGI function the function is called as WSGI application and buffered as response object + :class:`tuple` A tuple in the form ``(response, status, + headers)`` where `response` is any of the + types defined here, `status` is a string + or an integer and `headers` is a list of + a dictionary with header values. ======================= =========================================== :param rv: the return value from the view function + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. """ + status = headers = None + if isinstance(rv, tuple): + rv, status, headers = rv + (None,) * (3 - len(rv)) + if rv is None: raise ValueError('View function did not return a response') - if isinstance(rv, self.response_class): - return rv - if isinstance(rv, basestring): - return self.response_class(rv) - if isinstance(rv, tuple): - if len(rv) > 0 and isinstance(rv[0], self.response_class): - original = rv[0] - new_response = self.response_class('', *rv[1:]) - if len(rv) < 3: - # The args for the response class are - # response=None, status=None, headers=None, - # mimetype=None, content_type=None, ... - # so if there's at least 3 elements the rv - # tuple contains header information so the - # headers from rv[0] "win." - new_response.headers = original.headers - new_response.response = original.response - return new_response + + if not isinstance(rv, self.response_class): + # When we create a response object directly, we let the constructor + # set the headers and status. We do this because there can be + # some extra logic involved when creating these objects with + # specific values (like defualt content type selection). + if isinstance(rv, basestring): + rv = self.response_class(rv, headers=headers, status=status) + headers = status = None else: - return self.response_class(*rv) - return self.response_class.force_type(rv, request.environ) + rv = self.response_class.force_type(rv, request.environ) + + if status is not None: + if isinstance(status, basestring): + rv.status = status + else: + rv.status_code = status + if headers: + rv.headers.extend(headers) + + return rv def create_url_adapter(self, request): """Creates a URL adapter for the given request. The URL adapter diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 41efb196..0a4b1d9c 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -631,7 +631,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): return u'Hällo Wörld'.encode('utf-8') @app.route('/args') def from_tuple(): - return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' + return 'Meh', 400, { + 'X-Foo': 'Testing', + 'Content-Type': 'text/plain; charset=utf-8' + } c = app.test_client() self.assert_equal(c.get('/unicode').data, u'Hällo Wörld'.encode('utf-8')) self.assert_equal(c.get('/string').data, u'Hällo Wörld'.encode('utf-8')) @@ -677,16 +680,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = flask.make_response( flask.Response('', headers={'Content-Type': 'text/html'}), - 400, None, 'application/json') - self.assertEqual(rv.status_code, 400) - self.assertEqual(rv.headers['Content-Type'], 'application/json') - - rv = flask.make_response( - flask.Response('', mimetype='application/json'), - 400, {'Content-Type': 'text/html'}) + 400, [('X-Foo', 'bar')]) self.assertEqual(rv.status_code, 400) self.assertEqual(rv.headers['Content-Type'], 'text/html') - + self.assertEqual(rv.headers['X-Foo'], 'bar') def test_url_generation(self): app = flask.Flask(__name__) From 34bbd3100bbfb9a406a417a9fe4e8944aa87c629 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 16:11:35 +0100 Subject: [PATCH 0146/2940] Fixed a failing testcase --- flask/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 01f4081e..1be2daf7 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -726,7 +726,9 @@ class _PackageBoundObject(object): .. versionadded:: 0.9 """ - return {} + options = {} + options['cache_timeout'] = current_app.config['SEND_FILE_MAX_AGE_DEFAULT'] + return options def send_static_file(self, filename): """Function used internally to send static files from the static From 9bed20c07c40a163077b936f740a4e96a6213688 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 17:29:00 +0100 Subject: [PATCH 0147/2940] Added documentation for appcontext and teardown handlers --- docs/api.rst | 14 +++++- docs/appcontext.rst | 88 +++++++++++++++++++++++++++++++++ docs/contents.rst.inc | 1 + docs/extensiondev.rst | 100 ++++++++++++++++++++++++++------------ docs/reqcontext.rst | 26 ++-------- docs/signals.rst | 23 +++++++++ flask/app.py | 68 +++++++++++++++++++++++--- flask/ctx.py | 18 +++++-- flask/signals.py | 1 + flask/testsuite/appctx.py | 12 +++++ 10 files changed, 287 insertions(+), 64 deletions(-) create mode 100644 docs/appcontext.rst diff --git a/docs/api.rst b/docs/api.rst index 97332870..b09bcad5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -469,8 +469,18 @@ Signals .. data:: request_tearing_down This signal is sent when the application is tearing down the request. - This is always called, even if an error happened. No arguments are - provided. + This is always called, even if an error happened. An `exc` keyword + argument is passed with the exception that caused the teardown. + + .. versionchanged:: 0.9 + The `exc` parameter was added. + +.. data:: appcontext_tearing_down + + This signal is sent when the application is tearing down the + application context. This is always called, even if an error happened. + An `exc` keyword argument is passed with the exception that caused the + teardown. .. currentmodule:: None diff --git a/docs/appcontext.rst b/docs/appcontext.rst new file mode 100644 index 00000000..c331ffa5 --- /dev/null +++ b/docs/appcontext.rst @@ -0,0 +1,88 @@ +.. _app_context: + +The Application Context +======================= + +.. versionadded:: 0.9 + +One of the design ideas behind Flask is that there are two different +“states” in which code is executed. The application setup state in which +the application implicitly is on the module level. It starts when the +:class:`Flask` object is instantiated, and it implicitly ends when the +first request comes in. While the application is in this state a few +assumptions are true: + +- the programmer can modify the application object safely. +- no request handling happened so far +- you have to have a reference to the application object in order to + modify it, there is no magic proxy that can give you a reference to + the application object you're currently creating or modifying. + +On the contrast, during request handling, a couple of other rules exist: + +- while a request is active, the context local objects + (:data:`flask.request` and others) point to the current request. +- any code can get hold of these objects at any time. + +There is a third state which is sitting in between a little bit. +Sometimes you are dealing with an application in a way that is similar to +how you interact with applications during request handling just that there +is no request active. Consider for instance that you're sitting in an +interactive Python shell and interacting with the application, or a +command line application. + +The application context is what powers the :data:`~flask.current_app` +context local. + +Purpose of the Application Context +---------------------------------- + +The main reason for the application's context existance is that in the +past a bunch of functionality was attached to the request context in lack +of a better solution. Since one of the pillar's of Flask's design is that +you can have more than one application in the same Python process. + +So how does the code find the “right” application? In the past we +recommended passing applications around explicitly, but that caused issues +with libraries that were not designed with that in mind for libraries for +which it was too inconvenient to make this work. + +A common workaround for that problem was to use the +:data:`~flask.current_app` proxy later on, which was bound to the current +request's application reference. Since however creating such a request +context is an unnecessarily expensive operation in case there is no +request around, the application context was introduced. + +Creating an Application Context +------------------------------- + +To make an application context there are two ways. The first one is the +implicit one: whenever a request context is pushed, an application context +will be created alongside if this is necessary. As a result of that, you +can ignore the existance of the application context unless you need it. + +The second way is the explicit way using the +:meth:`~flask.Flask.app_context` method:: + + from flask import Flask, current_app + + app = Flask(__name__) + with app.app_context(): + # within this block, current_app points to app. + print current_app.name + +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. + +Locality of the Context +----------------------- + +The application context is created and destroyed as necessary. It never +moves between threads and it will not be shared between requests. As such +it is the perfect place to store database connection information and other +things. The internal stack object is called :data:`flask._app_ctx_stack`. +Extensions are free to store additional information on the topmost level, +assuming they pick a sufficiently unique name. + +For more information about that, see :ref:`extension-dev`. diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index a8ebc0d7..a1893c48 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -18,6 +18,7 @@ instructions for web development with Flask. config signals views + appcontext reqcontext blueprints extensions diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 60aa6c37..2511cec7 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -1,3 +1,5 @@ +.. _extension-dev: + Flask Extension Development =========================== @@ -152,6 +154,11 @@ 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 +circumstances store any application specific state and must be shareable +between different application. + The Extension Code ------------------ @@ -159,7 +166,13 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste:: import sqlite3 - from flask import _request_ctx_stack + # Find the stack on which we want to store the database connection. + # Starting with Flask 0.9, the _app_ctx_stack is the correct one, + # before that we need to use the _request_ctx_stack. + try: + from flask import _app_ctx_stack as stack + except ImportError: + from flask import _request_ctx_stack as stack class SQLite3(object): @@ -172,26 +185,28 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste:: self.app = None def init_app(self, app): - self.app = app - self.app.config.setdefault('SQLITE3_DATABASE', ':memory:') - self.app.teardown_request(self.teardown_request) - self.app.before_request(self.before_request) + app.config.setdefault('SQLITE3_DATABASE', ':memory:') + # Use the newstyle teardown_appcontext if it's available, + # otherwise fall back to the request context + if hasattr(app, 'teardown_appcontext'): + app.teardown_appcontext(self.teardown) + else: + app.teardown_request(self.teardown) def connect(self): return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) - def before_request(self): - ctx = _request_ctx_stack.top - ctx.sqlite3_db = self.connect() - - def teardown_request(self, exception): - ctx = _request_ctx_stack.top - ctx.sqlite3_db.close() + def teardown(self, exception): + ctx = stack.top + if hasattr(ctx, 'sqlite3_db'): + ctx.sqlite3_db.close() @property def connection(self): - ctx = _request_ctx_stack.top + ctx = stack.top if ctx is not None: + if not hasattr(ctx, 'sqlite3_db'): + ctx.sqlite3_db = self.connect() return ctx.sqlite3_db @@ -204,14 +219,19 @@ So here's what these lines of code do: factory pattern for creating applications. The ``init_app`` will set the configuration for the database, defaulting to an in memory database if no configuration is supplied. In addition, the ``init_app`` method attaches - ``before_request`` and ``teardown_request`` handlers. + the ``teardown`` handler. It will try to use the newstyle app context + handler and if it does not exist, falls back to the request context + one. 3. Next, we define a ``connect`` method that opens a database connection. -4. Then we set up the request handlers we bound to the app above. Note here - that we're attaching our database connection to the top request context via - ``_request_ctx_stack.top``. Extensions should use the top context and not the - ``g`` object to store things like database connections. -5. Finally, we add a ``connection`` property that simplifies access to the context's - database. +4. Finally, we add a ``connection`` property that on first access opens + the database connection and stores it on the context. + + Note here that we're attaching our database connection to the top + application context via ``_app_ctx_stack.top``. Extensions should use + the top context for storing their own information with a sufficiently + complex name. Note that we're falling back to the + ``_request_ctx_stack.top`` if the application is using an older + version of Flask that does not support it. So why did we decide on a class-based approach here? Because using our extension looks something like this:: @@ -241,19 +261,38 @@ for creating apps:: Keep in mind that supporting this factory pattern for creating apps is required for approved flask extensions (described below). +.. admonition:: Note on ``init_app`` -Using _request_ctx_stack ------------------------- + As you noticed, ``init_app`` does not assign ``app`` to ``self``. This + is intentional! Class based Flask extensions must only store the + application on the object when the application was passed to the + constructor. This tells the extension: I am not interested in using + multiple applications. -In the example above, before every request, a ``sqlite3_db`` variable is assigned -to ``_request_ctx_stack.top``. In a view function, this variable is accessible -using the ``connection`` property of ``SQLite3``. During the teardown of a -request, the ``sqlite3_db`` connection is closed. By using this pattern, the -*same* connection to the sqlite3 database is accessible to anything that needs it -for the duration of the request. + When the extension needs to find the current application and it does + not have a reference to it, it must either use the + :data:`~flask.current_app` context local or change the API in a way + that you can pass the application explicitly. -End-Of-Request Behavior ------------------------ + +Using _app_ctx_stack +-------------------- + +In the example above, before every request, a ``sqlite3_db`` variable is +assigned to ``_app_ctx_stack.top``. In a view function, this variable is +accessible using the ``connection`` property of ``SQLite3``. During the +teardown of a request, the ``sqlite3_db`` connection is closed. By using +this pattern, the *same* connection to the sqlite3 database is accessible +to anything that needs it for the duration of the request. + +If the :data:`~flask._app_ctx_stack` does not exist because the user uses +an old version of Flask, it is recommended to fall back to +:data:`~flask._request_ctx_stack` which is bound to a request. + +Teardown Behavior +----------------- + +*This is only relevant if you want to support Flask 0.6 and older* Due to the change in Flask 0.7 regarding functions that are run at the end of the request your extension will have to be extra careful there if it @@ -270,7 +309,6 @@ pattern is a good way to support both:: else: app.after_request(close_connection) - Strictly speaking the above code is wrong, because teardown functions are passed the exception and typically don't return anything. However because the return value is discarded this will just work assuming that the code diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 0249b88e..327afe6c 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -6,27 +6,7 @@ The Request Context This document describes the behavior in Flask 0.7 which is mostly in line with the old behavior but has some small, subtle differences. -One of the design ideas behind Flask is that there are two different -“states” in which code is executed. The application setup state in which -the application implicitly is on the module level. It starts when the -:class:`Flask` object is instantiated, and it implicitly ends when the -first request comes in. While the application is in this state a few -assumptions are true: - -- the programmer can modify the application object safely. -- no request handling happened so far -- you have to have a reference to the application object in order to - modify it, there is no magic proxy that can give you a reference to - the application object you're currently creating or modifying. - -On the contrast, during request handling, a couple of other rules exist: - -- while a request is active, the context local objects - (:data:`flask.request` and others) point to the current request. -- any code can get hold of these objects at any time. - -The magic that makes this works is internally referred in Flask as the -“request context”. +It is recommended that you read the :api:`app-context` chapter first. Diving into Context Locals -------------------------- @@ -107,6 +87,10 @@ the very top, :meth:`~flask.ctx.RequestContext.pop` removes it from the stack again. On popping the application's :func:`~flask.Flask.teardown_request` functions are also executed. +Another thing of note is that the request context will automatically also +create an :ref:`application context ` when it's pushed and +there is no application context for that application so far. + .. _callbacks-and-errors: Callbacks and Errors diff --git a/docs/signals.rst b/docs/signals.rst index a4cd4157..959c53bd 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -268,4 +268,27 @@ The following signals exist in Flask: from flask import request_tearing_down request_tearing_down.connect(close_db_connection, app) + As of Flask 0.9, this will also be passed an `exc` keyword argument + that has a reference to the exception that caused the teardown if + there was one. + +.. data:: flask.appcontext_tearing_down + :noindex: + + This signal is sent when the app context is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender, **extra): + session.close() + + from flask import request_tearing_down + appcontext_tearing_down.connect(close_db_connection, app) + + This will also be passed an `exc` keyword argument that has a reference + to the exception that caused the teardown if there was one. + .. _blinker: http://pypi.python.org/pypi/blinker diff --git a/flask/app.py b/flask/app.py index a0ffed66..8460f476 100644 --- a/flask/app.py +++ b/flask/app.py @@ -35,7 +35,7 @@ from .module import blueprint_is_module from .templating import DispatchingJinjaLoader, Environment, \ _default_template_ctx_processor from .signals import request_started, request_finished, got_request_exception, \ - request_tearing_down + request_tearing_down, appcontext_tearing_down # a lock used for logger initialization _logger_lock = Lock() @@ -364,6 +364,14 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.7 self.teardown_request_funcs = {} + #: A list of functions that are called when the application context + #: is destroyed. Since the application context is also torn down + #: if the request ends this is the place to store code that disconnects + #: from databases. + #: + #: .. versionadded:: 0.9 + self.teardown_appcontext_funcs = [] + #: A dictionary with lists of functions that can be used as URL #: value processor functions. Whenever a URL is built these functions #: are called to modify the dictionary of values in place. The key @@ -1106,10 +1114,42 @@ class Flask(_PackageBoundObject): that they will fail. If they do execute code that might fail they will have to surround the execution of these code by try/except statements and log ocurring errors. + + When a teardown function was called because of a exception it will + be passed an error object. """ self.teardown_request_funcs.setdefault(None, []).append(f) return f + @setupmethod + def teardown_appcontext(self, f): + """Registers a function to be called when the application context + ends. These functions are typically also called when the request + context is popped. + + Example:: + + ctx = app.app_context() + ctx.push() + ... + ctx.pop() + + When ``ctx.pop()`` is executed in the above example, the teardown + functions are called just before the app context moves from the + stack of active contexts. This becomes relevant if you are using + such constructs in tests. + + Since a request context typically also manages an application + context it would also be called when you pop a request context. + + When a teardown function was called because of an exception it will + be passed an error object. + + .. versionadded:: 0.9 + """ + self.teardown_appcontext_funcs.append(f) + return f + @setupmethod def context_processor(self, f): """Registers a template context processor function.""" @@ -1485,23 +1525,39 @@ class Flask(_PackageBoundObject): self.save_session(ctx.session, response) return response - def do_teardown_request(self): + def do_teardown_request(self, exc=None): """Called after the actual request dispatching and will call every as :meth:`teardown_request` decorated function. This is not actually called by the :class:`Flask` object itself but is always triggered when the request context is popped. That way we have a tighter control over certain resources under testing environments. + + .. versionchanged:: 0.9 + Added the `exc` argument. Previously this was always using the + current exception information. """ + if exc is None: + exc = sys.exc_info()[1] funcs = reversed(self.teardown_request_funcs.get(None, ())) bp = _request_ctx_stack.top.request.blueprint if bp is not None and bp in self.teardown_request_funcs: funcs = chain(funcs, reversed(self.teardown_request_funcs[bp])) - exc = sys.exc_info()[1] for func in funcs: rv = func(exc) - if rv is not None: - return rv - request_tearing_down.send(self) + request_tearing_down.send(self, exc=exc) + + def do_teardown_appcontext(self, exc=None): + """Called when an application context is popped. This works pretty + much the same as :meth:`do_teardown_request` but for the application + context. + + .. versionadded:: 0.9 + """ + if exc is None: + exc = sys.exc_info()[1] + for func in reversed(self.teardown_appcontext_funcs): + func(exc) + appcontext_tearing_down.send(self, exc=exc) def app_context(self): """Binds the application only. For as long as the application is bound diff --git a/flask/ctx.py b/flask/ctx.py index 887b2598..0ed5ea43 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -9,6 +9,8 @@ :license: BSD, see LICENSE for more details. """ +import sys + from werkzeug.exceptions import HTTPException from .globals import _request_ctx_stack, _app_ctx_stack @@ -86,8 +88,11 @@ class AppContext(object): """Binds the app context to the current context.""" _app_ctx_stack.push(self) - def pop(self): + def pop(self, exc=None): """Pops the app context.""" + if exc is None: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) rv = _app_ctx_stack.pop() assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ % (rv, self) @@ -197,13 +202,18 @@ class RequestContext(object): if self.session is None: self.session = self.app.make_null_session() - def pop(self): + def pop(self, exc=None): """Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. """ self.preserved = False - self.app.do_teardown_request() + if exc is None: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) rv = _request_ctx_stack.pop() assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ % (rv, self) @@ -231,7 +241,7 @@ class RequestContext(object): (tb is not None and self.app.preserve_context_on_exception): self.preserved = True else: - self.pop() + self.pop(exc_value) def __repr__(self): return '<%s \'%s\' [%s] of %s>' % ( diff --git a/flask/signals.py b/flask/signals.py index eeb763d4..78a77bd5 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -49,3 +49,4 @@ request_started = _signals.signal('request-started') request_finished = _signals.signal('request-finished') request_tearing_down = _signals.signal('request-tearing-down') got_request_exception = _signals.signal('got-request-exception') +appcontext_tearing_down = _signals.signal('appcontext-tearing-down') diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index c60dbc67..a4ad479b 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -53,6 +53,18 @@ class AppContextTestCase(FlaskTestCase): self.assert_equal(flask.current_app._get_current_object(), app) self.assert_equal(flask._app_ctx_stack.top, None) + def test_app_tearing_down(self): + cleanup_stuff = [] + app = flask.Flask(__name__) + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + with app.app_context(): + pass + + self.assert_equal(cleanup_stuff, [None]) + def suite(): suite = unittest.TestSuite() From cb54c462b809e36d12af571fa36affb4af3f7e96 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 17:32:37 +0100 Subject: [PATCH 0148/2940] Pass exc explicitly to the inner context. --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 0ed5ea43..413ca884 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -224,7 +224,7 @@ class RequestContext(object): # Get rid of the app as well if necessary. if self._pushed_application_context: - self._pushed_application_context.pop() + self._pushed_application_context.pop(exc) self._pushed_application_context = None def __enter__(self): From 32f845ea75ce57429aa383463be6654b2af06983 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 17:33:14 +0100 Subject: [PATCH 0149/2940] Added an example for using the db connection without the request --- docs/extensiondev.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 2511cec7..16a354e6 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -250,6 +250,17 @@ You can then use the database from views like this:: cur = db.connection.cursor() cur.execute(...) +Likewise if you are outside of a request but you are using Flask 0.9 or +later with the app context support, you can use the database in the same +way:: + + with app.app_context(): + cur = db.connection.cursor() + cur.execute(...) + +At the end of the `with` block the teardown handles will be executed +automatically. + Additionally, the ``init_app`` method is used to support the factory pattern for creating apps:: From 52f9cefbcd6df23f75bf93804a9e84c038536fe8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 17:35:16 +0100 Subject: [PATCH 0150/2940] More documentation updates for 0.9 --- docs/extensiondev.rst | 4 +++- docs/upgrading.rst | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 16a354e6..86c7c721 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -224,7 +224,9 @@ So here's what these lines of code do: one. 3. Next, we define a ``connect`` method that opens a database connection. 4. Finally, we add a ``connection`` property that on first access opens - the database connection and stores it on the context. + the database connection and stores it on the context. This is also + the recommended way to handling resources: fetch resources lazily the + first time they are used. Note here that we're attaching our database connection to the top application context via ``_app_ctx_stack.top``. Extensions should use diff --git a/docs/upgrading.rst b/docs/upgrading.rst index ab00624e..5955e552 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -34,6 +34,12 @@ the old behavior, you can add it easily by subclassing Flask:: return self.response_class(*rv) return Flask.make_response(self, rv) +If you have an extension that was using :data:`~flask._request_ctx_stack` +before, please consider changing to :data:`~flask._app_ctx_stack` if it +makes sense for your extension. This will for example be the case for +extensions that connect to databases. This will allow your users to +easier use your extension with more complex use cases outside of requests. + Version 0.8 ----------- From d26af4fd6dd71793cf6373c1c18c82349494e0aa Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 17:38:08 +0100 Subject: [PATCH 0151/2940] Fixed some smaller things in the docs --- docs/appcontext.rst | 2 +- docs/reqcontext.rst | 2 +- flask/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index c331ffa5..e9e1ad8f 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -1,4 +1,4 @@ -.. _app_context: +.. _app-context: The Application Context ======================= diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 327afe6c..4da5acd8 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -6,7 +6,7 @@ The Request Context This document describes the behavior in Flask 0.7 which is mostly in line with the old behavior but has some small, subtle differences. -It is recommended that you read the :api:`app-context` chapter first. +It is recommended that you read the :ref:`app-context` chapter first. Diving into Context Locals -------------------------- diff --git a/flask/__init__.py b/flask/__init__.py index f35ef328..b91f9395 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -25,7 +25,7 @@ from .helpers import url_for, jsonify, json_available, flash, \ get_template_attribute, make_response, safe_join from .globals import current_app, g, request, session, _request_ctx_stack, \ _app_ctx_stack -from .ctx import has_request_context +from .ctx import has_request_context, has_app_context from .module import Module from .blueprints import Blueprint from .templating import render_template, render_template_string From bcd00e5070caf6f5ff7639d3758f5595bc5507a1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Apr 2012 17:56:37 +0100 Subject: [PATCH 0152/2940] Fixed a typo --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 413ca884..16b03503 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -62,7 +62,7 @@ def has_request_context(): def has_app_context(): - """Worksl ike :func:`has_request_context` but for the application + """Works like :func:`has_request_context` but for the application context. You can also just do a boolean check on the :data:`current_app` object instead. From 9d09632dbfc0c08ab1a5290fbf282aad7d13e2f8 Mon Sep 17 00:00:00 2001 From: Sean Vieira Date: Mon, 9 Apr 2012 17:17:52 -0300 Subject: [PATCH 0153/2940] Fix spelling. --- flask/blueprints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/blueprints.py b/flask/blueprints.py index d81d3c73..9c557028 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -25,7 +25,7 @@ class BlueprintSetupState(object): #: a reference to the current application self.app = app - #: a reference to the blurprint that created this setup state. + #: a reference to the blueprint that created this setup state. self.blueprint = blueprint #: a dictionary with all options that were passed to the From acb61ae57b6fe9a82aaa9804c0e4bba8c2441746 Mon Sep 17 00:00:00 2001 From: Paul McMillan Date: Tue, 10 Apr 2012 13:14:38 -0700 Subject: [PATCH 0154/2940] Minor docs fix. --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index f7b6ee02..daaecb23 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -744,7 +744,7 @@ sessions work:: @app.route('/logout') def logout(): - # remove the username from the session if its there + # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) From a2eb5efcd8be834ca7e30e6392fa3a2067ad3a55 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Thu, 12 Apr 2012 19:14:52 +0300 Subject: [PATCH 0155/2940] few typos --- scripts/flaskext_compat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index 40c8c6b5..2f58ccc4 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -58,7 +58,7 @@ class ExtensionImporter(object): except ImportError: exc_type, exc_value, tb = sys.exc_info() # since we only establish the entry in sys.modules at the - # very this seems to be redundant, but if recursive imports + # end this seems to be redundant, but if recursive imports # happen we will call into the move import a second time. # On the second invocation we still don't have an entry for # fullname in sys.modules, but we will end up with the same @@ -71,7 +71,7 @@ class ExtensionImporter(object): # If it's an important traceback we reraise it, otherwise # we swallow it and try the next choice. The skipped frame - # is the one from __import__ above which we don't care about + # is the one from __import__ above which we don't care about. if self.is_important_traceback(realname, tb): raise exc_type, exc_value, tb.tb_next continue @@ -106,7 +106,7 @@ class ExtensionImporter(object): if module_name == important_module: return True - # Some python verisons will will clean up modules so early that the + # Some python versions will clean up modules so early that the # module name at that point is no longer set. Try guessing from # the filename then. filename = os.path.abspath(tb.tb_frame.f_code.co_filename) From ffbab00cd1c9ef89ab795b14b187334766556be7 Mon Sep 17 00:00:00 2001 From: Natan Date: Tue, 17 Apr 2012 19:28:28 -0700 Subject: [PATCH 0156/2940] Rectified rampant 'roule'. --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index b09bcad5..c329852e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -586,7 +586,7 @@ with the route parameter the view function is defined with the decorator instead of the `view_func` parameter. =============== ========================================================== -`rule` the URL roule as string +`rule` the URL rule as string `endpoint` the endpoint for the registered URL rule. Flask itself assumes that the name of the view function is the name of the endpoint if not explicitly stated. From 0d2ffc094b45e717439c679255f4aecd018e24d0 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 18 Apr 2012 15:44:07 -0400 Subject: [PATCH 0157/2940] Use 'venv' consistently for virtualenv directory. Pointed out by tri on #pocoo. --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 791c99f1..8dcae5a7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -61,7 +61,7 @@ information about how to do that. Once you have it installed, run the same commands as above, but without the `sudo` prefix. Once you have virtualenv installed, just fire up a shell and create -your own environment. I usually create a project folder and an `env` +your own environment. I usually create a project folder and a `venv` folder within:: $ mkdir myproject From b885edf81013f05161525db1eaf4e64fc6f5c2c4 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Wed, 18 Apr 2012 15:54:04 -0400 Subject: [PATCH 0158/2940] Fix typo pointed out by tri on #pocoo. --- docs/patterns/appdispatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/appdispatch.rst b/docs/patterns/appdispatch.rst index a2d1176f..4f6dc18b 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -90,7 +90,7 @@ the dynamic application creation. The perfect level for abstraction in that regard is the WSGI layer. You write your own WSGI application that looks at the request that comes and -and delegates it to your Flask application. If that application does not +delegates it to your Flask application. If that application does not exist yet, it is dynamically created and remembered:: from threading import Lock From 10c34e6652fac704c9c77a47d4853896f3030e34 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 19 Apr 2012 11:50:08 -0400 Subject: [PATCH 0159/2940] Reword 0.9 upgrade doc, thanks to plaes on #pocoo. --- docs/upgrading.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 5955e552..7226d60e 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -34,11 +34,12 @@ the old behavior, you can add it easily by subclassing Flask:: return self.response_class(*rv) return Flask.make_response(self, rv) -If you have an extension that was using :data:`~flask._request_ctx_stack` -before, please consider changing to :data:`~flask._app_ctx_stack` if it -makes sense for your extension. This will for example be the case for -extensions that connect to databases. This will allow your users to -easier use your extension with more complex use cases outside of requests. +If you maintain an extension that was using :data:`~flask._request_ctx_stack` +before, please consider changing to :data:`~flask._app_ctx_stack` if it makes +sense for your extension. For instance, the app context stack makes sense for +extensions which connect to databases. Using the app context stack instead of +the request stack will make extensions more readily handle use cases outside of +requests. Version 0.8 ----------- From a3cb2a33829ee517530d30cd920e5d652b358086 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 19 Apr 2012 11:51:38 -0400 Subject: [PATCH 0160/2940] Use American English for "behavior" in docs. Prompted by plaes on #pocoo, mitsuhiko confirmed to use American English. --- CHANGES | 8 ++++---- docs/design.rst | 2 +- docs/extensiondev.rst | 4 ++-- docs/htmlfaq.rst | 2 +- docs/patterns/jquery.rst | 2 +- docs/patterns/mongokit.rst | 2 +- docs/quickstart.rst | 2 +- docs/templating.rst | 2 +- flask/app.py | 2 +- flask/helpers.py | 10 +++++----- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index cb7b751e..83c1f4fe 100644 --- a/CHANGES +++ b/CHANGES @@ -153,7 +153,7 @@ Released on June 28th 2011, codename Grappa - Added :meth:`~flask.Flask.make_default_options_response` which can be used by subclasses to alter the default - behaviour for `OPTIONS` responses. + behavior for `OPTIONS` responses. - Unbound locals now raise a proper :exc:`RuntimeError` instead of an :exc:`AttributeError`. - Mimetype guessing and etag support based on file objects is now @@ -163,7 +163,7 @@ Released on June 28th 2011, codename Grappa - Static file handling for modules now requires the name of the static folder to be supplied explicitly. The previous autodetection was not reliable and caused issues on Google's App Engine. Until - 1.0 the old behaviour will continue to work but issue dependency + 1.0 the old behavior will continue to work but issue dependency warnings. - fixed a problem for Flask to run on jython. - added a `PROPAGATE_EXCEPTIONS` configuration variable that can be @@ -281,14 +281,14 @@ Released on July 6th 2010, codename Calvados the session cookie cross-subdomain wide. - autoescaping is no longer active for all templates. Instead it is only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``. - Inside templates this behaviour can be changed with the + Inside templates this behavior can be changed with the ``autoescape`` tag. - refactored Flask internally. It now consists of more than a single file. - :func:`flask.send_file` now emits etags and has the ability to do conditional responses builtin. - (temporarily) dropped support for zipped applications. This was a - rarely used feature and led to some confusing behaviour. + rarely used feature and led to some confusing behavior. - added support for per-package template and static-file directories. - removed support for `create_jinja_loader` which is no longer used in 0.5 due to the improved module support. diff --git a/docs/design.rst b/docs/design.rst index 6ca363a6..cc247f3b 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -48,7 +48,7 @@ allocated will be freed again. Another thing that becomes possible when you have an explicit object lying around in your code is that you can subclass the base class -(:class:`~flask.Flask`) to alter specific behaviour. This would not be +(:class:`~flask.Flask`) to alter specific behavior. This would not be possible without hacks if the object were created ahead of time for you based on a class that is not exposed to you. diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 86c7c721..59ca76c5 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -40,7 +40,7 @@ that it works with multiple Flask application instances at once. This is a requirement because many people will use patterns like the :ref:`app-factories` pattern to create their application as needed to aid unittests and to support multiple configurations. Because of that it is -crucial that your application supports that kind of behaviour. +crucial that your application supports that kind of behavior. Most importantly the extension must be shipped with a `setup.py` file and registered on PyPI. Also the development checkout link should work so @@ -145,7 +145,7 @@ initialization functions: classes: Classes work mostly like initialization functions but can later be - used to further change the behaviour. For an example look at how the + used to further change the behavior. For an example look at how the `OAuth extension`_ works: there is an `OAuth` object that provides some helper functions like `OAuth.remote_app` to create a reference to a remote application that uses OAuth. diff --git a/docs/htmlfaq.rst b/docs/htmlfaq.rst index 1da25f3d..b16f4cd5 100644 --- a/docs/htmlfaq.rst +++ b/docs/htmlfaq.rst @@ -52,7 +52,7 @@ Development of the HTML5 specification was started in 2004 under the name "Web Applications 1.0" by the Web Hypertext Application Technology Working Group, or WHATWG (which was formed by the major browser vendors Apple, Mozilla, and Opera) with the goal of writing a new and improved HTML -specification, based on existing browser behaviour instead of unrealistic +specification, based on existing browser behavior instead of unrealistic and backwards-incompatible specifications. For example, in HTML4 `` Date: Fri, 20 Apr 2012 09:07:58 -0400 Subject: [PATCH 0161/2940] Add detailed Apache httpd fastcgi configuration. --- docs/deploying/fastcgi.rst | 60 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index ebd68560..91824af0 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -51,6 +51,61 @@ can execute it: # chmod +x /var/www/yourapplication/yourapplication.fcgi +Configuring Apache +------------------ + +The example above is good enough for a basic Apache deployment but your `.fcgi` file will appear in your application URL e.g. www.example.com/yourapplication.fcgi/news/. There are few ways to resolve it. A preferable way is to use Apache ScriptAlias configuration directive:: + + <VirtualHost *> + ServerName example.com + ScriptAlias / /path/to/yourapplication.fcgi/ + </VirtualHost> + +Another way is to use a custom WSGI middleware. For example on a shared web hosting:: + + .htaccess + + <IfModule mod_fcgid.c> + AddHandler fcgid-script .fcgi + <Files ~ (\.fcgi)> + SetHandler fcgid-script + Options +FollowSymLinks +ExecCGI + </Files> + </IfModule> + + <IfModule mod_rewrite.c> + Options +FollowSymlinks + RewriteEngine On + RewriteBase / + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L] + </IfModule> + + yourapplication.fcgi + + #!/usr/bin/python + #: optional path to your local python site-packages folder + import sys + sys.path.insert(0, '<your_local_path>/lib/python2.6/site-packages') + + from flup.server.fcgi import WSGIServer + from yourapplication import app + + class ScriptNameStripper(object): + to_strip = '/yourapplication.fcgi' + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + environ['SCRIPT_NAME'] = '' + return self.app(environ, start_response) + + app = ScriptNameStripper(app) + + if __name__ == '__main__': + WSGIServer(app).run() + Configuring lighttpd -------------------- @@ -84,7 +139,6 @@ root. Also, see the Lighty docs for more information on `FastCGI and Python <http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ (note that explicitly passing a socket to run() is no longer necessary). - Configuring nginx ----------------- @@ -97,7 +151,7 @@ A basic flask FastCGI configuration for nginx looks like this:: location /yourapplication { try_files $uri @yourapplication; } location @yourapplication { include fastcgi_params; - fastcgi_split_path_info ^(/yourapplication)(.*)$; + fastcgi_split_path_info ^(/yourapplication)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; @@ -160,4 +214,4 @@ python path. Common problems are: .. _nginx: http://nginx.org/ .. _lighttpd: http://www.lighttpd.net/ .. _cherokee: http://www.cherokee-project.com/ -.. _flup: http://trac.saddi.com/flup +.. _flup: http://trac.saddi.com/flup \ No newline at end of file From fb011878857693659c6ec6f095fe3849803915e1 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Fri, 20 Apr 2012 09:20:20 -0400 Subject: [PATCH 0162/2940] Touch up fastcgi doc. --- docs/deploying/fastcgi.rst | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index 91824af0..b2801560 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -54,16 +54,19 @@ can execute it: Configuring Apache ------------------ -The example above is good enough for a basic Apache deployment but your `.fcgi` file will appear in your application URL e.g. www.example.com/yourapplication.fcgi/news/. There are few ways to resolve it. A preferable way is to use Apache ScriptAlias configuration directive:: +The example above is good enough for a basic Apache deployment but your `.fcgi` +file will appear in your application URL +e.g. example.com/yourapplication.fcgi/news/. There are few ways to configure +your application so that yourapplication.fcgi does not appear in the URL. A +preferable way is to use the ScriptAlias configuration directive:: <VirtualHost *> ServerName example.com ScriptAlias / /path/to/yourapplication.fcgi/ </VirtualHost> -Another way is to use a custom WSGI middleware. For example on a shared web hosting:: - - .htaccess +If you cannot set ScriptAlias, for example on an shared web host, you can use +WSGI middleware to remove yourapplication.fcgi from the URLs. Set .htaccess:: <IfModule mod_fcgid.c> AddHandler fcgid-script .fcgi @@ -81,7 +84,7 @@ Another way is to use a custom WSGI middleware. For example on a shared web host RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L] </IfModule> - yourapplication.fcgi +Set yourapplication.fcgi:: #!/usr/bin/python #: optional path to your local python site-packages folder @@ -128,16 +131,15 @@ A basic FastCGI configuration for lighttpd looks like that:: "^(/static.*)$" => "$1", "^(/.*)$" => "/yourapplication.fcgi$1" -Remember to enable the FastCGI, alias and rewrite modules. This -configuration binds the application to `/yourapplication`. If you want -the application to work in the URL root you have to work around a -lighttpd bug with the +Remember to enable the FastCGI, alias and rewrite modules. This configuration +binds the application to `/yourapplication`. If you want the application to +work in the URL root you have to work around a lighttpd bug with the :class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware. Make sure to apply it only if you are mounting the application the URL -root. Also, see the Lighty docs for more information on `FastCGI and -Python <http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ -(note that explicitly passing a socket to run() is no longer necessary). +root. Also, see the Lighty docs for more information on `FastCGI and Python +<http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModFastCGI>`_ (note that +explicitly passing a socket to run() is no longer necessary). Configuring nginx ----------------- @@ -151,7 +153,7 @@ A basic flask FastCGI configuration for nginx looks like this:: location /yourapplication { try_files $uri @yourapplication; } location @yourapplication { include fastcgi_params; - fastcgi_split_path_info ^(/yourapplication)(.*)$; + fastcgi_split_path_info ^(/yourapplication)(.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; @@ -214,4 +216,4 @@ python path. Common problems are: .. _nginx: http://nginx.org/ .. _lighttpd: http://www.lighttpd.net/ .. _cherokee: http://www.cherokee-project.com/ -.. _flup: http://trac.saddi.com/flup \ No newline at end of file +.. _flup: http://trac.saddi.com/flup From 0333c824bfa93fafabfb10b80f84d85abecda6bc Mon Sep 17 00:00:00 2001 From: Alex Vykalyuk <alekzvik@gmail.com> Date: Sat, 21 Apr 2012 22:27:24 +0300 Subject: [PATCH 0163/2940] Removed link to ep.io from quickstart --- docs/quickstart.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 46290d0b..b9cbdf32 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -839,7 +839,6 @@ immediately deploy to a hosted platform, all of which offer a free plan for small projects: - `Deploying Flask on Heroku <http://devcenter.heroku.com/articles/python>`_ -- `Deploying Flask on ep.io <https://www.ep.io/docs/quickstart/flask/>`_ - `Deploying WSGI on dotCloud <http://docs.dotcloud.com/services/python/>`_ with `Flask-specific notes <http://flask.pocoo.org/snippets/48/>`_ From d90f0afe39724040d0be92df054e2b1438886134 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Sat, 21 Apr 2012 18:40:02 -0400 Subject: [PATCH 0164/2940] Add test for jsonify padded=False, #495. --- flask/testsuite/helpers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index be268645..4781d2d9 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -73,14 +73,17 @@ class JSONTestCase(FlaskTestCase): @app.route('/dict') def return_dict(): return flask.jsonify(d) + @app.route("/unpadded") + def return_padded_false(): + return flask.jsonify(d, padded=False) @app.route("/padded") - def return_padded_json(): + def return_padded_true(): return flask.jsonify(d, padded=True) @app.route("/padded_custom") def return_padded_json_custom_callback(): return flask.jsonify(d, padded='my_func_name') c = app.test_client() - for url in '/kw', '/dict': + for url in '/kw', '/dict', '/unpadded': rv = c.get(url) self.assert_equal(rv.mimetype, 'application/json') self.assert_equal(flask.json.loads(rv.data), d) From 36194697ae8371f42d3041c1f89b6f2e77c34826 Mon Sep 17 00:00:00 2001 From: ekoka <verysimple@gmail.com> Date: Sat, 21 Apr 2012 23:36:08 -0300 Subject: [PATCH 0165/2940] Update flask/app.py --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 4c00c36b..4328c35b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1468,7 +1468,7 @@ class Flask(_PackageBoundObject): """ funcs = self.url_default_functions.get(None, ()) if '.' in endpoint: - bp = endpoint.split('.', 1)[0] + bp = endpoint.rsplit('.', 1)[0] funcs = chain(funcs, self.url_default_functions.get(bp, ())) for func in funcs: func(endpoint, values) From bb31188ec3882e9a6c6035b7a65ca257d424e31a Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Sun, 22 Apr 2012 12:30:15 -0400 Subject: [PATCH 0166/2940] Add a BuildError hook to url_for, #456. --- flask/app.py | 20 ++++++++++++++++++++ flask/helpers.py | 14 ++++++++++++++ flask/testsuite/basic.py | 12 ++++++++++++ 3 files changed, 46 insertions(+) diff --git a/flask/app.py b/flask/app.py index 4c00c36b..c0a9dac3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -329,6 +329,17 @@ class Flask(_PackageBoundObject): #: decorator. self.error_handler_spec = {None: self._error_handlers} + #: If not `None`, this function is called when :meth:`url_for` raises + #: :exc:`~werkzeug.routing.BuildError`, with the call signature:: + #: + #: self.build_error_handler(error, endpoint, **values) + #: + #: Here, `error` is the instance of `BuildError`, and `endpoint` and + #: `**values` are the arguments passed into :meth:`url_for`. + #: + #: .. versionadded:: 0.9 + self.build_error_handler = None + #: A dictionary with lists of functions that should be called at the #: beginning of the request. The key of the dictionary is the name of #: the blueprint this function is active for, `None` for all requests. @@ -1473,6 +1484,15 @@ class Flask(_PackageBoundObject): for func in funcs: func(endpoint, values) + def handle_build_error(self, error, endpoint, **values): + """Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`. + + Calls :attr:`build_error_handler` if it is not `None`. + """ + if self.build_error_handler is None: + raise error + return self.build_error_handler(error, endpoint, **values) + def preprocess_request(self): """Called before the actual request dispatching and will call every as :meth:`before_request` decorated function. diff --git a/flask/helpers.py b/flask/helpers.py index 238d7df7..21b010b4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -20,6 +20,7 @@ import mimetypes from time import time from zlib import adler32 from threading import RLock +from werkzeug.routing import BuildError from werkzeug.urls import url_quote # try to load the best simplejson implementation available. If JSON @@ -214,6 +215,10 @@ def url_for(endpoint, **values): .. versionadded:: 0.9 The `_anchor` and `_method` parameters were added. + .. versionadded:: 0.9 + Calls :meth:`Flask.handle_build_error` on + :exc:`~werkzeug.routing.BuildError`. + :param endpoint: the endpoint of the URL (name of the function) :param values: the variable arguments of the URL rule :param _external: if set to `True`, an absolute URL is generated. @@ -260,6 +265,15 @@ def url_for(endpoint, **values): anchor = values.pop('_anchor', None) method = values.pop('_method', None) appctx.app.inject_url_defaults(endpoint, values) + try: + rv = url_adapter.build(endpoint, values, method=method, + force_external=external) + except BuildError, error: + values['_external'] = external + values['_anchor'] = anchor + values['_method'] = method + return appctx.app.handle_build_error(error, endpoint, **values) + rv = url_adapter.build(endpoint, values, method=method, force_external=external) if anchor is not None: diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 0a4b1d9c..d138c45e 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -19,6 +19,7 @@ from threading import Thread from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_date +from werkzeug.routing import BuildError class BasicFunctionalityTestCase(FlaskTestCase): @@ -695,6 +696,17 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(flask.url_for('hello', name='test x', _external=True), 'http://localhost/hello/test%20x') + def test_build_error_handler(self): + app = flask.Flask(__name__) + with app.test_request_context(): + self.assertRaises(BuildError, flask.url_for, 'spam') + def handler(error, endpoint, **values): + # Just a test. + return '/test_handler/' + app.build_error_handler = handler + with app.test_request_context(): + self.assert_equal(flask.url_for('spam'), '/test_handler/') + def test_custom_converters(self): from werkzeug.routing import BaseConverter class ListConverter(BaseConverter): From 8c8c524ddb791c2f7eed47b6d2e4317faf06a659 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Sun, 22 Apr 2012 12:51:31 -0400 Subject: [PATCH 0167/2940] Re-raise BuildError with traceback. --- flask/app.py | 6 +++++- flask/testsuite/basic.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index c0a9dac3..97dc5bde 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1490,7 +1490,11 @@ class Flask(_PackageBoundObject): Calls :attr:`build_error_handler` if it is not `None`. """ if self.build_error_handler is None: - raise error + exc_type, exc_value, tb = sys.exc_info() + if exc_value is error: + raise exc_type, exc_value, tb + else: + raise error return self.build_error_handler(error, endpoint, **values) def preprocess_request(self): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index d138c45e..cf7590cb 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -698,8 +698,23 @@ class BasicFunctionalityTestCase(FlaskTestCase): def test_build_error_handler(self): app = flask.Flask(__name__) + + # Test base case, a URL which results in a BuildError. with app.test_request_context(): self.assertRaises(BuildError, flask.url_for, 'spam') + + # Verify the error is re-raised if not the current exception. + try: + with app.test_request_context(): + flask.url_for('spam') + except BuildError, error: + pass + try: + raise RuntimeError('Test case where BuildError is not current.') + except RuntimeError: + self.assertRaises(BuildError, app.handle_build_error, error, 'spam') + + # Test a custom handler. def handler(error, endpoint, **values): # Just a test. return '/test_handler/' From 028229d0ff531db172f8cda52210de8903dbc14b Mon Sep 17 00:00:00 2001 From: Alex Vykalyuk <alekzvik@gmail.com> Date: Mon, 23 Apr 2012 00:32:48 +0300 Subject: [PATCH 0168/2940] Fixed typo in docs/quickstart --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b9cbdf32..e8b71ca9 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -526,7 +526,7 @@ deal with that problem. To access parameters submitted in the URL (``?key=value``) you can use the :attr:`~flask.request.args` attribute:: - searchword = request.args.get('q', '') + searchword = request.args.get('key', '') We recommend accessing URL parameters with `get` or by catching the `KeyError` because users might change the URL and presenting them a 400 From 148c50abf983eb8bbd8ecb565c9f98912048af46 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Mon, 23 Apr 2012 21:20:47 -0400 Subject: [PATCH 0169/2940] Document url_for BuildError hook. --- flask/app.py | 1 + flask/helpers.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/flask/app.py b/flask/app.py index 97dc5bde..67383bbe 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1492,6 +1492,7 @@ class Flask(_PackageBoundObject): if self.build_error_handler is None: exc_type, exc_value, tb = sys.exc_info() if exc_value is error: + # exception is current, raise in context of original traceback. raise exc_type, exc_value, tb else: raise error diff --git a/flask/helpers.py b/flask/helpers.py index 21b010b4..5560d38c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -212,6 +212,40 @@ def url_for(endpoint, **values): For more information, head over to the :ref:`Quickstart <url-building>`. + To integrate applications, :class:`Flask` has a hook to intercept URL build + errors through :attr:`Flask.build_error_handler`. The `url_for` function + results in a :exc:`~werkzeug.routing.BuildError` when the current app does + not have a URL for the given endpoint and values. When it does, the + :data:`~flask.current_app` calls its :attr:`~Flask.build_error_handler` if + it is not `None`, which can return a string to use as the result of + `url_for` (instead of `url_for`'s default to raise the + :exc:`~werkzeug.routing.BuildError` exception) or re-raise the exception. + An example:: + + def external_url_handler(error, endpoint, **values): + "Looks up an external URL when `url_for` cannot build a URL." + # This is an example of hooking the build_error_handler. + # Here, lookup_url is some utility function you've built + # which looks up the endpoint in some external URL registry. + url = lookup_url(endpoint, **values) + if url is None: + # External lookup did not have a URL. + # Re-raise the BuildError, in context of original traceback. + exc_type, exc_value, tb = sys.exc_info() + if exc_value is error: + raise exc_type, exc_value, tb + else: + raise error + # url_for will use this result, instead of raising BuildError. + return url + + app.build_error_handler = external_url_handler + + Here, `error` is the instance of :exc:`~werkzeug.routing.BuildError`, and + `endpoint` and `**values` are the arguments passed into `url_for`. Note + that this is for building URLs outside the current application, and not for + handling 404 NotFound errors. + .. versionadded:: 0.9 The `_anchor` and `_method` parameters were added. From 2262ce4915aec0dfffa8e71244ebe10f72d11111 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Mon, 23 Apr 2012 21:36:28 -0400 Subject: [PATCH 0170/2940] Skip template leak test when not CPython2.7, #452. --- flask/testsuite/regression.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py index 51a866a4..bc37afc4 100644 --- a/flask/testsuite/regression.py +++ b/flask/testsuite/regression.py @@ -72,9 +72,12 @@ class MemoryTestCase(FlaskTestCase): # Trigger caches fire() - with self.assert_no_leak(): - for x in xrange(10): - fire() + # This test only works on CPython 2.7. + if sys.version_info >= (2, 7) and \ + not hasattr(sys, 'pypy_translation_info'): + with self.assert_no_leak(): + for x in xrange(10): + fire() def suite(): From b31f2d9a640c154e41de6d9631e95cf105e96e1f Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Mon, 23 Apr 2012 21:46:53 -0400 Subject: [PATCH 0171/2940] Require Werkzeug>=0.7, #449. --- README | 9 +++++---- setup.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README b/README index 7d5ada23..317080a7 100644 --- a/README +++ b/README @@ -17,10 +17,11 @@ ~ What do I need? - Jinja 2.4 and Werkzeug 0.6.1. `pip` or `easy_install` will - install them for you if you do `easy_install Flask`. - I encourage you to use a virtualenv. Check the docs for - complete installation and usage instructions. + Jinja 2.4 and Werkzeug 0.7 or later. + `pip` or `easy_install` will install them for you if you do + `pip install Flask`. I encourage you to use a virtualenv. + Check the docs for complete installation and usage + instructions. ~ Where are the docs? diff --git a/setup.py b/setup.py index 8169a517..fdc4653e 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ setup( zip_safe=False, platforms='any', install_requires=[ - 'Werkzeug>=0.6.1', + 'Werkzeug>=0.7', 'Jinja2>=2.4' ], classifiers=[ From ff5ee034b8c71a79d3f29c7b7a1ad27f6a8893e3 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Mon, 23 Apr 2012 21:47:28 -0400 Subject: [PATCH 0172/2940] Touch up README. --- README | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README b/README index 317080a7..7297f5db 100644 --- a/README +++ b/README @@ -6,7 +6,7 @@ ~ What is Flask? Flask is a microframework for Python based on Werkzeug - and Jinja2. It's intended for small scale applications + and Jinja2. It's intended for getting started very quickly and was developed with best intentions in mind. ~ Is it ready? @@ -51,3 +51,5 @@ Either use the #pocoo IRC channel on irc.freenode.net or ask on the mailinglist: http://flask.pocoo.org/mailinglist/ + + See http://flask.pocoo.org/community/ for more resources. From 7c79ce6e418f07a49be8c25a4c5a40e5347be257 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Mon, 23 Apr 2012 23:42:58 -0400 Subject: [PATCH 0173/2940] Revise foreword and Becoming Big docs, #484. --- docs/advanced_foreword.rst | 65 ++++++++++++++-------------- docs/becomingbig.rst | 88 ++++++++++++++++++++++---------------- docs/contents.rst.inc | 1 + docs/foreword.rst | 66 ++++++++++++++-------------- 4 files changed, 118 insertions(+), 102 deletions(-) diff --git a/docs/advanced_foreword.rst b/docs/advanced_foreword.rst index cc1a1843..1831dd59 100644 --- a/docs/advanced_foreword.rst +++ b/docs/advanced_foreword.rst @@ -1,27 +1,26 @@ +.. _advanced_foreword: + Foreword for Experienced Programmers ==================================== -This chapter is for programmers who have worked with other frameworks in the -past, and who may have more specific or esoteric concerns that the typical -user. +Thread-Locals in Flask +---------------------- -Threads in Flask ----------------- - -One of the design decisions with Flask was that simple tasks should be simple; +One of the design decisions in Flask was that simple tasks should be simple; they should not take a lot of code and yet they should not limit you. Because -of that we made a few design choices that some people might find surprising or -unorthodox. For example, Flask uses thread-local objects internally so that -you don’t have to pass objects around from function to function within a -request in order to stay threadsafe. While this is a really easy approach and -saves you a lot of time, it might also cause some troubles for very large -applications because changes on these thread-local objects can happen anywhere -in the same thread. In order to solve these problems we don’t hide the thread -locals for you but instead embrace them and provide you with a lot of tools to -make it as pleasant as possible to work with them. +of that, Flask has few design choices that some people might find surprising or +unorthodox. For example, Flask uses thread-local objects internally so that you +don’t have to pass objects around from function to function within a request in +order to stay threadsafe. This approach is convenient, but requires a valid +request context for dependency injection or when attempting to reuse code which +uses a value pegged to the request. The Flask project is honest about +thread-locals, does not hide them, and calls out in the code and documentation +where they are used. -Web Development is Dangerous ----------------------------- +Develop for the Web with Caution +-------------------------------- + +Always keep security in mind when building web applications. If you write a web application, you are probably allowing users to register and leave their data on your server. The users are entrusting you with data. @@ -30,22 +29,22 @@ you still want that data to be stored securely. Unfortunately, there are many ways the security of a web application can be compromised. Flask protects you against one of the most common security -problems of modern web applications: cross-site scripting (XSS). Unless -you deliberately mark insecure HTML as secure, Flask and the underlying -Jinja2 template engine have you covered. But there are many more ways to -cause security problems. +problems of modern web applications: cross-site scripting (XSS). Unless you +deliberately mark insecure HTML as secure, Flask and the underlying Jinja2 +template engine have you covered. But there are many more ways to cause +security problems. -The documentation will warn you about aspects of web development that -require attention to security. Some of these security concerns -are far more complex than one might think, and we all sometimes underestimate -the likelihood that a vulnerability will be exploited - until a clever -attacker figures out a way to exploit our applications. And don't think -that your application is not important enough to attract an attacker. -Depending on the kind of attack, chances are that automated bots are -probing for ways to fill your database with spam, links to malicious -software, and the like. +The documentation will warn you about aspects of web development that require +attention to security. Some of these security concerns are far more complex +than one might think, and we all sometimes underestimate the likelihood that a +vulnerability will be exploited - until a clever attacker figures out a way to +exploit our applications. And don't think that your application is not +important enough to attract an attacker. Depending on the kind of attack, +chances are that automated bots are probing for ways to fill your database with +spam, links to malicious software, and the like. -So always keep security in mind when doing web development. +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 ---------------------- @@ -65,3 +64,5 @@ using Python 2.6 and 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 <http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/>`_. + +Continue to :ref:`installation` or the :ref:`quickstart`. diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst index 20a0186e..ca803060 100644 --- a/docs/becomingbig.rst +++ b/docs/becomingbig.rst @@ -3,45 +3,57 @@ Becoming Big ============ -Your application is becoming more and more complex? If you suddenly -realize that Flask does things in a way that does not work out for your -application there are ways to deal with that. +Here are your options when growing your codebase or scaling your application. -Flask is powered by Werkzeug and Jinja2, two libraries that are in use at -a number of large websites out there and all Flask does is bring those -two together. Being a microframework Flask does not do much more than -combining existing libraries - there is not a lot of code involved. -What that means for large applications is that it's very easy to take the -code from Flask and put it into a new module within the applications and -expand on that. +Read the Source. +---------------- -Flask is designed to be extended and modified in a couple of different -ways: +Flask started in part to demonstrate how to build your own framework on top of +existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it +developed, it became useful to a wide audience. As you grow your codebase, +don't just use Flask -- understand it. Read the source. Flask's code is +written to be read; it's documentation published so you can use its internal +APIs. Flask sticks to documented APIs in upstream libraries, and documents its +internal utilities so that you can find the hook points needed for your +project. -- Flask extensions. For a lot of reusable functionality you can create - extensions. For extensions a number of hooks exist throughout Flask - with signals and callback functions. +Hook. Extend. +------------- -- Subclassing. The majority of functionality can be changed by creating - a new subclass of the :class:`~flask.Flask` class and overriding - methods provided for this exact purpose. +The :ref:`api` docs are full of available overrides, hook points, and +:ref:`signals`. You can provide custom classes for things like the request and +response objects. Dig deeper on the APIs you use, and look for the +customizations which are available out of the box in a Flask release. Look for +ways in which your project can be refactored into a collection of utilities and +Flask extensions. Explore the many extensions in the community, and look for +patterns to build your own extensions if you do not find the tools you need. -- Forking. If nothing else works out you can just take the Flask - codebase at a given point and copy/paste it into your application - and change it. Flask is designed with that in mind and makes this - incredible easy. You just have to take the package and copy it - into your application's code and rename it (for example to - `framework`). Then you can start modifying the code in there. +Subclass. +--------- -Why consider Forking? +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`. + +Wrap with middleware. --------------------- -The majority of code of Flask is within Werkzeug and Jinja2. These -libraries do the majority of the work. Flask is just the paste that glues -those together. For every project there is the point where the underlying -framework gets in the way (due to assumptions the original developers -had). This is natural because if this would not be the case, the -framework would be a very complex system to begin with which causes a +The :ref:`app-dispatch` chapter shows in detail how to apply middleware. You +can introduce WSGI middleware to wrap your Flask instances and introduce fixes +and changes at the layer between your Flask application and your HTTP +server. Werkzeug includes several `middlewares +<http://werkzeug.pocoo.org/docs/middlewares/>`_. + +Fork. +----- + +If none of the above options work, fork Flask. The majority of code of Flask +is within Werkzeug and Jinja2. These libraries do the majority of the work. +Flask is just the paste that glues those together. For every project there is +the point where the underlying framework gets in the way (due to assumptions +the original developers had). This is natural because if this would not be the +case, the framework would be a very complex system to begin with which causes a steep learning curve and a lot of user frustration. This is not unique to Flask. Many people use patched and modified @@ -55,8 +67,8 @@ Furthermore integrating upstream changes can be a complex process, depending on the number of changes. Because of that, forking should be the very last resort. -Scaling like a Pro ------------------- +Scale like a pro. +----------------- For many web applications the complexity of the code is less an issue than the scaling for the number of users or data entries expected. Flask by @@ -78,11 +90,11 @@ majority of servers are using either threads, greenlets or separate processes to achieve concurrency which are all methods well supported by the underlying Werkzeug library. -Dialogue with the Community +Discuss with the community. --------------------------- -The Flask developers are very interested to keep everybody happy, so as -soon as you find an obstacle in your way, caused by Flask, don't hesitate -to contact the developers on the mailinglist or IRC channel. The best way -for the Flask and Flask-extension developers to improve it for larger +The Flask developers keep the framework accessible to users with codebases big +and small. If you find an obstacle in your way, caused by Flask, don't hesitate +to contact the developers on the mailinglist or IRC channel. The best way for +the Flask and Flask extension developers to improve the tools for larger applications is getting feedback from users. diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index a1893c48..b60c7a03 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -9,6 +9,7 @@ instructions for web development with Flask. :maxdepth: 2 foreword + advanced_foreword installation quickstart tutorial/index diff --git a/docs/foreword.rst b/docs/foreword.rst index b186aba6..167f2f41 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -8,48 +8,50 @@ should or should not be using it. What does "micro" mean? ----------------------- -“Micro” does not mean that your whole web application has to fit into -a single Python file (although it certainly can). Nor does it mean -that Flask is lacking in functionality. The "micro" in microframework -means Flask aims to keep the core simple but extensible. Flask won't make -many decisions for you, such as what database to use. Those decisions that -it does make, such as what templating engine to use, are easy to change. -Everything else is up to you, so that Flask can be everything you need -and nothing you don't. +“Micro” does not mean that your whole web application has to fit into a single +Python file, although it certainly can. Nor does it mean that Flask is lacking +in functionality. The "micro" in microframework means Flask aims to keep the +core simple but extensible. Flask won't make many decisions for you, such as +what database to use. Those decisions that it does make, such as what +templating engine to use, are easy to change. Everything else is up to you, so +that Flask can be everything you need and nothing you don't. By default, Flask does not include a database abstraction layer, form validation or anything else where different libraries already exist that can -handle that. Instead, FLask extensions add such functionality to your -application as if it was implemented in Flask itself. Numerous extensions +handle that. Instead, Flask supports extensions to add such functionality to +your application as if it was implemented in Flask itself. Numerous extensions provide database integration, form validation, upload handling, various open -authentication technologies, and more. Flask may be "micro", but the -possibilities are endless. +authentication technologies, and more. Flask may be "micro", but it's ready for +production use on a variety of needs. -Convention over Configuration +Configuration and Conventions ----------------------------- -Flask is based on convention over configuration, which means that many things -are preconfigured. For example, by convention templates and static files are -stored in subdirectories within the application's Python source tree. While -this can be changed you usually don't have to. We want to minimize the time -you need to spend in order to get up and running, without assuming things -about your needs. +Flask has many configuration values, with sensible defaults, and a few +conventions when getting started. By convention templates and static files are +stored in subdirectories within the application's Python source tree, with the +names `templates` and `static` respectively. While this can be changed you +usually don't have to, especially when getting started. -Growing Up ----------- +Growing with Flask +------------------ -Since Flask is based on a very solid foundation there is not a lot of code in -Flask itself. As such it's easy to adapt even for large applications and we -are making sure that you can either configure it as much as possible by -subclassing things or by forking the entire codebase. If you are interested -in that, check out the :ref:`becomingbig` chapter. +Once you have Flask up and running, you'll find a variety of extensions +available in the community to integrate your project for production. The Flask +core team reviews extensions and ensures approved extensions do not break with +future releases. -If you are curious about the Flask design principles, head over to the section -about :ref:`design`. +As your codebase grows, you are free to make the design decisions appropriate +for your project. Flask will continue to provide a very simple glue layer to +the best that Python has to offer. You can implement advanced patterns in +SQLAlchemy or another database tool, introduce non-relational data persistence +as appropriate, and take advantage of framework-agnostic tools built for WSGI, +the Python web interface. -For the Stalwart and Wizened... -------------------------------- +Flask includes many hooks to customize its behavior. Should you need more +customization, the Flask class is built for subclassing. If you are interested +in that, check out the :ref:`becomingbig` chapter. If you are curious about +the Flask design principles, head over to the section about :ref:`design`. -If you're more curious about the minutiae of Flask's implementation, and -whether its structure is right for your needs, read the +Continue to :ref:`installation`, the :ref:`quickstart`, or the :ref:`advanced_foreword`. From 26da6a5365e1fd229932f167a299128f30fae154 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Tue, 24 Apr 2012 01:48:05 -0400 Subject: [PATCH 0174/2940] Use default send_file max-age consistently. Prior to this commit, the send_file max-age hook and config were only used for the static file handler. Now they are used when calling helpers.send_file directly. --- CHANGES | 17 ++++++------ docs/config.rst | 11 +++++--- flask/app.py | 6 ----- flask/helpers.py | 51 ++++++++++++++++++++++------------- flask/testsuite/blueprints.py | 23 ++++++++++++++++ flask/testsuite/helpers.py | 25 +++++++++++------ 6 files changed, 88 insertions(+), 45 deletions(-) diff --git a/CHANGES b/CHANGES index 83c1f4fe..537f6544 100644 --- a/CHANGES +++ b/CHANGES @@ -53,14 +53,15 @@ Relase date to be decided, codename to be chosen. - View functions can now return a tuple with the first instance being an instance of :class:`flask.Response`. This allows for returning ``jsonify(error="error msg"), 400`` from a view function. -- :class:`flask.Flask` now provides a `get_send_file_options` hook for - subclasses to override behavior of serving static files from Flask when using - :meth:`flask.Flask.send_static_file` based on keywords in - :func:`flask.helpers.send_file`. This hook is provided a filename, which for - example allows changing cache controls by file extension. The default - max-age for `send_static_file` can be configured through a new - ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether - the `get_send_file_options` hook is used. +- :class:`~flask.Flask` and :class:`~flask.Blueprint` now provide a + :meth:`~flask.Flask.get_send_file_max_age` hook for subclasses to override + behavior of serving static files from Flask when using + :meth:`flask.Flask.send_static_file` (used for the default static file + handler) and :func:`~flask.helpers.send_file`. This hook is provided a + filename, which for example allows changing cache controls by file extension. + The default max-age for `send_file` and static files can be configured + through a new ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, which is + used in the default `get_send_file_max_age` implementation. - Fixed an assumption in sessions implementation which could break message flashing on sessions implementations which use external storage. - Changed the behavior of tuple return values from functions. They are no diff --git a/docs/config.rst b/docs/config.rst index 86bfb0d1..7a32fb84 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -111,12 +111,15 @@ The following configuration values are used internally by Flask: content length greater than this by returning a 413 status code. ``SEND_FILE_MAX_AGE_DEFAULT``: Default cache control max age to use with - :meth:`flask.Flask.send_static_file`, in + :meth:`~flask.Flask.send_static_file` (the + default static file handler) and + :func:`~flask.send_file`, in seconds. Override this value on a per-file basis using the - :meth:`flask.Flask.get_send_file_options` and - :meth:`flask.Blueprint.get_send_file_options` - hooks. Defaults to 43200 (12 hours). + :meth:`~flask.Flask.get_send_file_max_age` + hook on :class:`~flask.Flask` or + :class:`~flask.Blueprint`, + respectively. Defaults to 43200 (12 hours). ``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will not execute the error handlers of HTTP exceptions but instead treat the diff --git a/flask/app.py b/flask/app.py index 67383bbe..5f809abb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1041,12 +1041,6 @@ class Flask(_PackageBoundObject): self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \ .append((code_or_exception, f)) - def get_send_file_options(self, filename): - # Override: Hooks in SEND_FILE_MAX_AGE_DEFAULT config. - options = super(Flask, self).get_send_file_options(filename) - options['cache_timeout'] = self.config['SEND_FILE_MAX_AGE_DEFAULT'] - return options - @setupmethod def template_filter(self, name=None): """A decorator that is used to register custom template filter. diff --git a/flask/helpers.py b/flask/helpers.py index 5560d38c..05f84ef7 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -406,7 +406,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=60 * 60 * 12, conditional=False): + cache_timeout=None, conditional=False): """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 @@ -420,10 +420,6 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, guessing requires a `filename` or an `attachment_filename` to be provided. - Note `get_send_file_options` in :class:`flask.Flask` hooks the - ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default - cache_timeout. - Please never pass filenames to this function from user sources without checking them first. Something like this is usually sufficient to avoid security problems:: @@ -443,6 +439,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, able to, otherwise attach an etag yourself. This functionality will be removed in Flask 1.0 + .. versionchanged:: 0.9 + cache_timeout pulls its default from application config, when None. + :param filename_or_fp: the filename of the file to send. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. @@ -459,7 +458,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, differs from the file's filename. :param add_etags: set to `False` to disable attaching of etags. :param conditional: set to `True` to enable conditional responses. - :param cache_timeout: the timeout in seconds for the headers. + + :param cache_timeout: the timeout in seconds for the headers. When `None` + (default), this value is set by + :meth:`~Flask.get_send_file_max_age` of + :data:`~flask.current_app`. """ mtime = None if isinstance(filename_or_fp, basestring): @@ -523,6 +526,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv.last_modified = int(mtime) rv.cache_control.public = True + if cache_timeout is None: + cache_timeout = current_app.get_send_file_max_age(filename) if cache_timeout: rv.cache_control.max_age = cache_timeout rv.expires = int(time() + cache_timeout) @@ -757,26 +762,31 @@ class _PackageBoundObject(object): return FileSystemLoader(os.path.join(self.root_path, self.template_folder)) - def get_send_file_options(self, filename): - """Provides keyword arguments to send to :func:`send_from_directory`. + def get_send_file_max_age(self, filename): + """Provides default cache_timeout for the :func:`send_file` functions. + + By default, this function returns ``SEND_FILE_MAX_AGE_DEFAULT`` from + the configuration of :data:`~flask.current_app`. + + Static file functions such as :func:`send_from_directory` use this + function, and :func:`send_file` calls this function on + :data:`~flask.current_app` when the given cache_timeout is `None`. If a + cache_timeout is given in :func:`send_file`, that timeout is used; + otherwise, this method is called. This allows subclasses to change the behavior when sending files based on the filename. For example, to set the cache timeout for .js files - to 60 seconds (note the options are keywords for :func:`send_file`):: + to 60 seconds:: class MyFlask(flask.Flask): - def get_send_file_options(self, filename): - options = super(MyFlask, self).get_send_file_options(filename) - if filename.lower().endswith('.js'): - options['cache_timeout'] = 60 - options['conditional'] = True - return options + def get_send_file_max_age(self, name): + if name.lower().endswith('.js'): + return 60 + return flask.Flask.get_send_file_max_age(self, name) .. versionadded:: 0.9 """ - options = {} - options['cache_timeout'] = current_app.config['SEND_FILE_MAX_AGE_DEFAULT'] - return options + return current_app.config['SEND_FILE_MAX_AGE_DEFAULT'] def send_static_file(self, filename): """Function used internally to send static files from the static @@ -786,8 +796,11 @@ class _PackageBoundObject(object): """ if not self.has_static_folder: raise RuntimeError('No static folder for this object') + # Ensure get_send_file_max_age is called in all cases. + # Here, we ensure get_send_file_max_age is called for Blueprints. + cache_timeout = self.get_send_file_max_age(filename) return send_from_directory(self.static_folder, filename, - **self.get_send_file_options(filename)) + cache_timeout=cache_timeout) def open_resource(self, resource, mode='rb'): """Opens a resource from the application's resource folder. To see diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 5f3d3ab3..c9622121 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -386,6 +386,29 @@ class BlueprintTestCase(FlaskTestCase): with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') + def test_default_static_cache_timeout(self): + app = flask.Flask(__name__) + class MyBlueprint(flask.Blueprint): + def get_send_file_max_age(self, filename): + return 100 + + blueprint = MyBlueprint('blueprint', __name__, static_folder='static') + app.register_blueprint(blueprint) + + # try/finally, in case other tests use this app for Blueprint tests. + max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT'] + try: + with app.test_request_context(): + unexpected_max_age = 3600 + if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == unexpected_max_age: + unexpected_max_age = 7200 + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = unexpected_max_age + rv = blueprint.send_static_file('index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 100) + finally: + app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default + def test_templates_list(self): from blueprintapp import app templates = sorted(app.jinja_env.list_templates()) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 4781d2d9..a0e60aac 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -237,28 +237,37 @@ class SendfileTestCase(FlaskTestCase): app = flask.Flask(__name__) # default cache timeout is 12 hours with app.test_request_context(): + # Test with static file handler. rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 12 * 60 * 60) + # Test again with direct use of send_file utility. + rv = flask.send_file('static/index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 12 * 60 * 60) app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600 with app.test_request_context(): + # Test with static file handler. rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 3600) - # override get_send_file_options with some new values and check them + # Test again with direct use of send_file utility. + rv = flask.send_file('static/index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 3600) class StaticFileApp(flask.Flask): - def get_send_file_options(self, filename): - opts = super(StaticFileApp, self).get_send_file_options(filename) - opts['cache_timeout'] = 10 - # this test catches explicit inclusion of the conditional - # keyword arg in the guts - opts['conditional'] = True - return opts + def get_send_file_max_age(self, filename): + return 10 app = StaticFileApp(__name__) with app.test_request_context(): + # Test with static file handler. rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 10) + # Test again with direct use of send_file utility. + rv = flask.send_file('static/index.html') + cc = parse_cache_control_header(rv.headers['Cache-Control']) + self.assert_equal(cc.max_age, 10) class LoggingTestCase(FlaskTestCase): From 33bae1a8dcadbf56fb19ad814fa516b6468bb2cb Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Wed, 18 Apr 2012 20:46:07 -0400 Subject: [PATCH 0175/2940] Add Flask.request_globals_class to customize g. Requested by toothr on #pocoo. --- CHANGES | 2 ++ flask/app.py | 7 ++++++- flask/ctx.py | 3 ++- flask/testsuite/appctx.py | 10 ++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 537f6544..10cd1c08 100644 --- a/CHANGES +++ b/CHANGES @@ -66,6 +66,8 @@ Relase date to be decided, codename to be chosen. flashing on sessions implementations which use external storage. - Changed the behavior of tuple return values from functions. They are no longer arguments to the response object, they now have a defined meaning. +- Added :attr:`flask.Flask.request_globals_class` to allow a specific class to + be used on creation of the :data:`~flask.g` instance of each request. Version 0.8.1 ------------- diff --git a/flask/app.py b/flask/app.py index 5f809abb..1a2961e1 100644 --- a/flask/app.py +++ b/flask/app.py @@ -28,7 +28,7 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ find_package from .wrappers import Request, Response from .config import ConfigAttribute, Config -from .ctx import RequestContext, AppContext +from .ctx import RequestContext, AppContext, _RequestGlobals from .globals import _request_ctx_stack, request from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module @@ -148,6 +148,11 @@ class Flask(_PackageBoundObject): #: :class:`~flask.Response` for more information. response_class = Response + #: The class that is used for the :data:`~flask.g` instance. + #: + #: .. versionadded:: 0.9 + request_globals_class = _RequestGlobals + #: The debug flag. Set this to `True` to enable debugging of the #: application. In debug mode the debugger will kick in when an unhandled #: exception ocurrs and the integrated server will automatically reload diff --git a/flask/ctx.py b/flask/ctx.py index 16b03503..cf197d05 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -18,6 +18,7 @@ from .module import blueprint_is_module class _RequestGlobals(object): + """A plain object.""" pass @@ -139,7 +140,7 @@ class RequestContext(object): self.app = app self.request = app.request_class(environ) self.url_adapter = app.create_url_adapter(self.request) - self.g = _RequestGlobals() + self.g = app.request_globals_class() self.flashes = None self.session = None diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index a4ad479b..1dcdb406 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -65,6 +65,16 @@ class AppContextTestCase(FlaskTestCase): self.assert_equal(cleanup_stuff, [None]) + def test_custom_request_globals_class(self): + class CustomRequestGlobals(object): + def __init__(self): + self.spam = 'eggs' + app = flask.Flask(__name__) + app.request_globals_class = CustomRequestGlobals + with app.test_request_context(): + self.assert_equal( + flask.render_template_string('{{ g.spam }}'), 'eggs') + def suite(): suite = unittest.TestSuite() From e78e2a1641e5b7ad538d93154ee59445f4d4eaf7 Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Tue, 24 Apr 2012 02:10:16 -0400 Subject: [PATCH 0176/2940] Document example request_globals_class use cases. --- flask/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/app.py b/flask/app.py index 1a2961e1..aa64f4c6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -150,6 +150,13 @@ class Flask(_PackageBoundObject): #: The class that is used for the :data:`~flask.g` instance. #: + #: Example use cases for a custom class: + #: + #: 1. Store arbitrary attributes on flask.g. + #: 2. Add a property for lazy per-request database connectors. + #: 3. Return None instead of AttributeError on expected attributes. + #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. + #: #: .. versionadded:: 0.9 request_globals_class = _RequestGlobals From 12dcba8849d153c7e13e99b6bcf57922e1a97240 Mon Sep 17 00:00:00 2001 From: ekoka <verysimple@gmail.com> Date: Tue, 24 Apr 2012 05:32:52 -0300 Subject: [PATCH 0177/2940] Update flask/testsuite/basic.py --- flask/testsuite/basic.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 0a4b1d9c..c2acca57 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -911,6 +911,29 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(c.get('/de/').data, '/de/about') self.assert_equal(c.get('/de/about').data, '/foo') self.assert_equal(c.get('/foo').data, '/en/about') + + def test_inject_blueprint_url_defaults(self): + app = flask.Flask(__name__) + bp = flask.Blueprint('foo.bar.baz', __name__, + template_folder='template') + + @bp.url_defaults + def bp_defaults(endpoint, values): + values['page'] = 'login' + @bp.route('/<page>') + def view(page): pass + + app.register_blueprint(bp) + + values = dict() + app.inject_url_defaults('foo.bar.baz.view', values) + expected = dict(page='login') + self.assert_equal(values, expected) + + with app.test_request_context('/somepage'): + url = flask.url_for('foo.bar.baz.view') + expected = '/login' + self.assert_equal(url, expected) def test_debug_mode_complains_after_first_request(self): app = flask.Flask(__name__) From 2053d04db0f303430f5f6c5bc6e97b8bec46c399 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 8 May 2012 11:56:11 +0100 Subject: [PATCH 0178/2940] Improved interface for the URL build error handler --- flask/app.py | 43 +++++++++++++++++++++------------------- flask/helpers.py | 5 +++-- flask/testsuite/basic.py | 6 +++--- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/flask/app.py b/flask/app.py index aa64f4c6..40090ab0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -19,7 +19,7 @@ from itertools import chain from functools import update_wrapper from werkzeug.datastructures import ImmutableDict -from werkzeug.routing import Map, Rule, RequestRedirect +from werkzeug.routing import Map, Rule, RequestRedirect, BuildError from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed, BadRequest @@ -341,16 +341,14 @@ class Flask(_PackageBoundObject): #: decorator. self.error_handler_spec = {None: self._error_handlers} - #: If not `None`, this function is called when :meth:`url_for` raises - #: :exc:`~werkzeug.routing.BuildError`, with the call signature:: - #: - #: self.build_error_handler(error, endpoint, **values) - #: - #: Here, `error` is the instance of `BuildError`, and `endpoint` and - #: `**values` are the arguments passed into :meth:`url_for`. + #: A list of functions that are called when :meth:`url_for` raises a + #: :exc:`~werkzeug.routing.BuildError`. Each function registered here + #: is called with `error`, `endpoint` and `values`. If a function + #: returns `None` or raises a `BuildError` the next function is + #: tried. #: #: .. versionadded:: 0.9 - self.build_error_handler = None + self.url_build_error_handlers = [] #: A dictionary with lists of functions that should be called at the #: beginning of the request. The key of the dictionary is the name of @@ -1490,19 +1488,24 @@ class Flask(_PackageBoundObject): for func in funcs: func(endpoint, values) - def handle_build_error(self, error, endpoint, **values): + def handle_url_build_error(self, error, endpoint, values): """Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`. - - Calls :attr:`build_error_handler` if it is not `None`. """ - if self.build_error_handler is None: - exc_type, exc_value, tb = sys.exc_info() - if exc_value is error: - # exception is current, raise in context of original traceback. - raise exc_type, exc_value, tb - else: - raise error - return self.build_error_handler(error, endpoint, **values) + exc_type, exc_value, tb = sys.exc_info() + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + if rv is not None: + return rv + except BuildError, error: + pass + + # At this point we want to reraise the exception. If the error is + # still the same one we can reraise it with the original traceback, + # otherwise we raise it from here. + if error is exc_value: + raise exc_type, exc_value, tb + raise error def preprocess_request(self): """Called before the actual request dispatching and will diff --git a/flask/helpers.py b/flask/helpers.py index 05f84ef7..e633e1b9 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -11,7 +11,6 @@ from __future__ import with_statement -import imp import os import sys import pkgutil @@ -303,10 +302,12 @@ def url_for(endpoint, **values): rv = url_adapter.build(endpoint, values, method=method, force_external=external) except BuildError, error: + # We need to inject the values again so that the app callback can + # deal with that sort of stuff. values['_external'] = external values['_anchor'] = anchor values['_method'] = method - return appctx.app.handle_build_error(error, endpoint, **values) + return appctx.app.handle_url_build_error(error, endpoint, values) rv = url_adapter.build(endpoint, values, method=method, force_external=external) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index cf7590cb..55b66f78 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -712,13 +712,13 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: raise RuntimeError('Test case where BuildError is not current.') except RuntimeError: - self.assertRaises(BuildError, app.handle_build_error, error, 'spam') + self.assertRaises(BuildError, app.handle_url_build_error, error, 'spam', {}) # Test a custom handler. - def handler(error, endpoint, **values): + def handler(error, endpoint, values): # Just a test. return '/test_handler/' - app.build_error_handler = handler + app.url_build_error_handlers.append(handler) with app.test_request_context(): self.assert_equal(flask.url_for('spam'), '/test_handler/') From dbfd406a21191c26c2987ffd11f7c49b8733cd82 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 8 May 2012 12:51:26 +0100 Subject: [PATCH 0179/2940] Added required_methods --- CHANGES | 2 ++ docs/api.rst | 4 ++++ flask/app.py | 9 ++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 10cd1c08..2c3f904a 100644 --- a/CHANGES +++ b/CHANGES @@ -68,6 +68,8 @@ Relase date to be decided, codename to be chosen. longer arguments to the response object, they now have a defined meaning. - Added :attr:`flask.Flask.request_globals_class` to allow a specific class to be used on creation of the :data:`~flask.g` instance of each request. +- Added `required_methods` attribute to view functions to force-add methods + on registration. Version 0.8.1 ------------- diff --git a/docs/api.rst b/docs/api.rst index c329852e..c97a6928 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -636,6 +636,10 @@ some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior: decorators that want to customize the `OPTIONS` response on a per-view basis. +- `required_methods`: if this attribute is set, Flask will always add + these methods when registering a URL rule even if the methods were + explicitly overriden in the ``route()`` call. + Full example:: def index(): diff --git a/flask/app.py b/flask/app.py index 40090ab0..a91b0f60 100644 --- a/flask/app.py +++ b/flask/app.py @@ -915,6 +915,10 @@ class Flask(_PackageBoundObject): # a tuple of only `GET` as default. if methods is None: methods = getattr(view_func, 'methods', None) or ('GET',) + methods = set(methods) + + # Methods that should always be added + required_methods = set(getattr(view_func, 'required_methods', ())) # starting with Flask 0.8 the view_func object can disable and # force-enable the automatic options handling. @@ -923,11 +927,14 @@ class Flask(_PackageBoundObject): if provide_automatic_options is None: if 'OPTIONS' not in methods: - methods = tuple(methods) + ('OPTIONS',) provide_automatic_options = True + required_methods.add('OPTIONS') else: provide_automatic_options = False + # Add the required methods now. + methods |= required_methods + # due to a werkzeug bug we need to make sure that the defaults are # None if they are an empty dictionary. This should not be necessary # with Werkzeug 0.7 From 086348e2f2874fb701048d5e1390cfe674de1f70 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 8 May 2012 13:14:32 +0100 Subject: [PATCH 0180/2940] Added after_this_request decorator. --- CHANGES | 1 + docs/api.rst | 2 ++ flask/__init__.py | 3 ++- flask/app.py | 2 +- flask/ctx.py | 30 ++++++++++++++++++++++++++++++ flask/testsuite/basic.py | 15 +++++++++++++++ 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 2c3f904a..28a0790c 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,7 @@ Relase date to be decided, codename to be chosen. be used on creation of the :data:`~flask.g` instance of each request. - Added `required_methods` attribute to view functions to force-add methods on registration. +- Added :func:`flask.after_this_request`. Version 0.8.1 ------------- diff --git a/docs/api.rst b/docs/api.rst index c97a6928..dcb54baf 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -289,6 +289,8 @@ Useful Functions and Classes .. autofunction:: make_response +.. autofunction:: after_this_request + .. autofunction:: send_file .. autofunction:: send_from_directory diff --git a/flask/__init__.py b/flask/__init__.py index b91f9395..de84bb69 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -25,7 +25,8 @@ from .helpers import url_for, jsonify, json_available, flash, \ get_template_attribute, make_response, safe_join from .globals import current_app, g, request, session, _request_ctx_stack, \ _app_ctx_stack -from .ctx import has_request_context, has_app_context +from .ctx import has_request_context, has_app_context, \ + after_this_request from .module import Module from .blueprints import Blueprint from .templating import render_template, render_template_string diff --git a/flask/app.py b/flask/app.py index a91b0f60..9254c398 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1555,7 +1555,7 @@ class Flask(_PackageBoundObject): """ ctx = _request_ctx_stack.top bp = ctx.request.blueprint - funcs = () + funcs = ctx._after_request_functions if bp is not None and bp in self.after_request_funcs: funcs = reversed(self.after_request_funcs[bp]) if None in self.after_request_funcs: diff --git a/flask/ctx.py b/flask/ctx.py index cf197d05..52eddcb2 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -11,6 +11,7 @@ import sys +from functools import partial from werkzeug.exceptions import HTTPException from .globals import _request_ctx_stack, _app_ctx_stack @@ -30,6 +31,31 @@ def _push_app_if_necessary(app): return ctx +def after_this_request(f): + """Executes a function after this request. This is useful to modify + response objects. The function is passed the response object and has + to return the same or a new one. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + _request_ctx_stack.top._after_request_functions.append(f) + return f + + def has_request_context(): """If you have code that wants to test if a request context is there or not this function can be used. For instance, you may want to take advantage @@ -153,6 +179,10 @@ class RequestContext(object): # context, it will be stored there self._pushed_application_context = None + # Functions that should be executed after the request on the response + # object. These will even be called in case of an error. + self._after_request_functions = [] + self.match_request() # XXX: Support for deprecated functionality. This is going away with diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 55b66f78..ba6c2705 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -411,6 +411,21 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_('after' in evts) self.assert_equal(rv, 'request|after') + def test_after_request_processing(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + @flask.after_this_request + def foo(response): + response.headers['X-Foo'] = 'a header' + return response + return 'Test' + c = app.test_client() + resp = c.get('/') + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.headers['X-Foo'], 'a header') + def test_teardown_request_handler(self): called = [] app = flask.Flask(__name__) From 444698d42b6cd2bb356d3ac6a7fa9f2be7e0df55 Mon Sep 17 00:00:00 2001 From: Alex Vykalyuk <alekzvik@gmail.com> Date: Mon, 14 May 2012 23:39:27 +0300 Subject: [PATCH 0181/2940] Changed docstring according to docs. --- scripts/flaskext_compat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index 2f58ccc4..cb0b436c 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -9,6 +9,7 @@ Usage:: import flaskext_compat + flaskext_compat.activate() from flask.ext import foo :copyright: (c) 2011 by Armin Ronacher. From 447afc3525b009ed369943e13437d98e07898bdc Mon Sep 17 00:00:00 2001 From: Marc Abramowitz <marc@marc-abramowitz.com> Date: Sun, 27 May 2012 18:02:54 -0700 Subject: [PATCH 0182/2940] Fix failing test: "AssertionError: 'application/javascript' != 'application/json'" in flask/testsuite/helpers.py", line 88 --- flask/helpers.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index e633e1b9..72a961a8 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -133,13 +133,17 @@ def jsonify(*args, **kwargs): """ if __debug__: _assert_have_json() + + padded = kwargs.get('padded', False) if 'padded' in kwargs: - if isinstance(kwargs['padded'], str): - callback = request.args.get(kwargs['padded']) or 'jsonp' + del kwargs['padded'] + + if padded: + if isinstance(padded, str): + callback = request.args.get(padded) or 'jsonp' else: callback = request.args.get('callback') or \ request.args.get('jsonp') or 'jsonp' - del kwargs['padded'] json_str = json.dumps(dict(*args, **kwargs), indent=None) content = str(callback) + "(" + json_str + ")" return current_app.response_class(content, mimetype='application/javascript') From 2c8cbeb0c0b577f8588aa832c4bb763b8e5b3b82 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz <marc@marc-abramowitz.com> Date: Sun, 27 May 2012 18:31:07 -0700 Subject: [PATCH 0183/2940] Add .travis.yml --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d02cae02 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python + +python: + - 2.5 + - 2.6 + - 2.7 + - pypy + +before_install: pip install simplejson + +script: python setup.py test From 99aaacb1a99891aa2eb57f6fe9b8a0c3f87cb454 Mon Sep 17 00:00:00 2001 From: Natan L <kuyanatan.nlao@gmail.com> Date: Wed, 30 May 2012 20:23:02 -0700 Subject: [PATCH 0184/2940] Emended extensiondev.rst. --- docs/extensiondev.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 59ca76c5..d266e1a2 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -47,10 +47,10 @@ registered on PyPI. Also the development checkout link should work so that people can easily install the development version into their virtualenv without having to download the library by hand. -Flask extensions must be licensed as BSD or MIT or a more liberal license -to be enlisted on 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. +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 +that the Flask Extension Registry is a moderated place and libraries will +be reviewed upfront if they behave as required. "Hello Flaskext!" ----------------- From 5c2aa7a9210acb4b16b86d5cda5264a2f5d11aa2 Mon Sep 17 00:00:00 2001 From: Ben Rousch <brousch@gmail.com> Date: Tue, 12 Jun 2012 14:26:10 -0300 Subject: [PATCH 0185/2940] Added link to extensions in "Hook. Extend." section. --- docs/becomingbig.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst index ca803060..8d531620 100644 --- a/docs/becomingbig.rst +++ b/docs/becomingbig.rst @@ -25,8 +25,9 @@ The :ref:`api` docs are full of available overrides, hook points, and response objects. Dig deeper on the APIs you use, and look for the customizations which are available out of the box in a Flask release. Look for ways in which your project can be refactored into a collection of utilities and -Flask extensions. Explore the many extensions in the community, and look for -patterns to build your own extensions if you do not find the tools you need. +Flask extensions. Explore the many `extensions +<http://flask.pocoo.org/extensions/>` in the community, and look for patterns to +build your own extensions if you do not find the tools you need. Subclass. --------- From 4b21e2d38ccf66f236c5f1c8a10bd9d7904f2599 Mon Sep 17 00:00:00 2001 From: Massimo Santini <santini@dsi.unimi.it> Date: Wed, 13 Jun 2012 16:43:34 +0300 Subject: [PATCH 0186/2940] I think it should check that cache_timeout is not None to allow for a (I hope legale) value of 0 for such parameter. --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 72a961a8..18502a53 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -533,7 +533,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv.cache_control.public = True if cache_timeout is None: cache_timeout = current_app.get_send_file_max_age(filename) - if cache_timeout: + if cache_timeout is not None: rv.cache_control.max_age = cache_timeout rv.expires = int(time() + cache_timeout) From 5bbf8bdcd9619fa15a3380f99fe6d66d08b1f783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20G=C3=B3mez?= <web@ramiro.org> Date: Sun, 17 Jun 2012 02:22:02 +0300 Subject: [PATCH 0187/2940] Update master --- docs/deploying/fastcgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index b2801560..0e2f6cdc 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -102,7 +102,7 @@ Set yourapplication.fcgi:: def __call__(self, environ, start_response): environ['SCRIPT_NAME'] = '' - return self.app(environ, start_response) + return self.app(environ, start_response) app = ScriptNameStripper(app) From 6809ffccf2b8b94633717f839b42ae8e3fbca1c3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 17 Jun 2012 14:13:53 +0100 Subject: [PATCH 0188/2940] Don't build websites with travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index d02cae02..8cd434d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,7 @@ python: before_install: pip install simplejson script: python setup.py test + +branches: + except: + - website From b04827283ea36cc456367f1ac4e0700b90b71283 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 17 Jun 2012 14:17:22 +0100 Subject: [PATCH 0189/2940] Removed padded JSON (JSONP) again. The implementation was not clean and generally the needs for padded json are disappearing now that all browsers support cross site communication with the regular xmlhttprequest. --- flask/helpers.py | 26 -------------------------- flask/testsuite/helpers.py | 19 +------------------ 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 18502a53..71bc3142 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -118,35 +118,9 @@ def jsonify(*args, **kwargs): information about this, have a look at :ref:`json-security`. .. versionadded:: 0.2 - - .. versionadded:: 0.9 - If the ``padded`` argument is true, the JSON object will be padded - for JSONP calls and the response mimetype will be changed to - ``application/javascript``. By default, the request arguments ``callback`` - and ``jsonp`` will be used as the name for the callback function. - This will work with jQuery and most other JavaScript libraries - by default. - - If the ``padded`` argument is a string, jsonify will look for - the request argument with the same name and use that value as the - callback-function name. """ if __debug__: _assert_have_json() - - padded = kwargs.get('padded', False) - if 'padded' in kwargs: - del kwargs['padded'] - - if padded: - if isinstance(padded, str): - callback = request.args.get(padded) or 'jsonp' - else: - callback = request.args.get('callback') or \ - request.args.get('jsonp') or 'jsonp' - json_str = json.dumps(dict(*args, **kwargs), indent=None) - content = str(callback) + "(" + json_str + ")" - return current_app.response_class(content, mimetype='application/javascript') return current_app.response_class(json.dumps(dict(*args, **kwargs), indent=None if request.is_xhr else 2), mimetype='application/json') diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index a0e60aac..816f6cd8 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -73,28 +73,11 @@ class JSONTestCase(FlaskTestCase): @app.route('/dict') def return_dict(): return flask.jsonify(d) - @app.route("/unpadded") - def return_padded_false(): - return flask.jsonify(d, padded=False) - @app.route("/padded") - def return_padded_true(): - return flask.jsonify(d, padded=True) - @app.route("/padded_custom") - def return_padded_json_custom_callback(): - return flask.jsonify(d, padded='my_func_name') c = app.test_client() - for url in '/kw', '/dict', '/unpadded': + for url in '/kw', '/dict': rv = c.get(url) self.assert_equal(rv.mimetype, 'application/json') self.assert_equal(flask.json.loads(rv.data), d) - for get_arg in 'callback=funcName', 'jsonp=funcName': - rv = c.get('/padded?' + get_arg) - self.assert_( rv.data.startswith("funcName(") ) - self.assert_( rv.data.endswith(")") ) - rv_json = rv.data.split('(')[1].split(')')[0] - self.assert_equal(flask.json.loads(rv_json), d) - rv = c.get('/padded_custom?my_func_name=funcName') - self.assert_( rv.data.startswith("funcName(") ) def test_json_attr(self): app = flask.Flask(__name__) From 7b1c8fd15b619aee9eb6bc53dd50bd2d7e9bea3d Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 17 Jun 2012 14:22:15 +0100 Subject: [PATCH 0190/2940] Added #522 in modified version --- docs/deploying/cgi.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/deploying/cgi.rst b/docs/deploying/cgi.rst index a2fba90d..1de9bd2c 100644 --- a/docs/deploying/cgi.rst +++ b/docs/deploying/cgi.rst @@ -35,12 +35,23 @@ Usually there are two ways to configure the server. Either just copy the `.cgi` into a `cgi-bin` (and use `mod_rewrite` or something similar to rewrite the URL) or let the server point to the file directly. -In Apache for example you can put a like like this into the config: +In Apache for example you can put something like this into the config: .. sourcecode:: apache ScriptAlias /app /path/to/the/application.cgi +On shared webhosting, though, you might not have access to your Apache config. +In this case, a file called `.htaccess`, sitting in the public directory you want +your app to be available, works too but the `ScriptAlias` directive won't +work in that case: + +.. sourcecode:: apache + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f # Don't interfere with static files + RewriteRule ^(.*)$ /path/to/the/application.cgi/$1 [L] + For more information consult the documentation of your webserver. .. _App Engine: http://code.google.com/appengine/ From 1f3e667b5d9ffb60c218c250df27144793a5acdb Mon Sep 17 00:00:00 2001 From: Matt Wright <mdw1980@gmail.com> Date: Mon, 18 Jun 2012 18:33:17 -0300 Subject: [PATCH 0191/2940] Fix documention for `after_this_request` --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 52eddcb2..f64ab04a 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -41,7 +41,7 @@ def after_this_request(f): @app.route('/') def index(): @after_this_request - def add_header(): + def add_header(response): response.headers['X-Foo'] = 'Parachute' return response return 'Hello World!' From 1f82d02b33dad8ac7a4aa49e690767027690fe1a Mon Sep 17 00:00:00 2001 From: bev-a-tron <beverly.a.lau@gmail.com> Date: Mon, 25 Jun 2012 13:31:11 -0400 Subject: [PATCH 0192/2940] Fixes #519 by adding return statement --- docs/quickstart.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e8b71ca9..53ef38d4 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -514,8 +514,9 @@ attributes mentioned above:: return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' - # this is executed if the request method was GET or the - # credentials were invalid + # the code below this is executed if the request method + # was GET or the credentials were invalid + return render_template('login.html', error=error) What happens if the key does not exist in the `form` attribute? In that case a special :exc:`KeyError` is raised. You can catch it like a From 8071f11328ab2b767608c5c63d0bad72d9408120 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 26 Jun 2012 17:18:59 +0200 Subject: [PATCH 0193/2940] Fixed an issue with the new path finding logic --- flask/helpers.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index e633e1b9..f714a699 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -616,17 +616,29 @@ def get_root_path(import_name): Not to be confused with the package path returned by :func:`find_package`. """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) + if mod is not None and hasattr(mod, '__file__'): + return os.path.dirname(os.path.abspath(mod.__file__)) + + # Next attempt: check the loader. loader = pkgutil.get_loader(import_name) + + # Loader does not exist or we're referring to an unloaded main module + # or a main module without path (interactive sessions), go with the + # current working directory. if loader is None or import_name == '__main__': - # import name is not found, or interactive/main module return os.getcwd() + # For .egg, zipimporter does not have get_filename until Python 2.7. + # Some other loaders might exhibit the same behavior. if hasattr(loader, 'get_filename'): filepath = loader.get_filename(import_name) else: # Fall back to imports. __import__(import_name) filepath = sys.modules[import_name].__file__ + # filepath is import_name.py for a module, or __init__.py for a package. return os.path.dirname(os.path.abspath(filepath)) From 558750494f6f99b1272218c35d86e6452484e77c Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Wed, 27 Jun 2012 12:08:01 +0100 Subject: [PATCH 0194/2940] Removed unnecessary import --- flask/ctx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 52eddcb2..3f3fbc52 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -11,7 +11,6 @@ import sys -from functools import partial from werkzeug.exceptions import HTTPException from .globals import _request_ctx_stack, _app_ctx_stack From 43c6a1ede87da5c20d8b5c7db995cf2d22eb40b2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Wed, 27 Jun 2012 12:22:39 +0100 Subject: [PATCH 0195/2940] Fixed a comment --- flask/ctx.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 3f3fbc52..90858aa4 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -179,7 +179,8 @@ class RequestContext(object): self._pushed_application_context = None # Functions that should be executed after the request on the response - # object. These will even be called in case of an error. + # object. These will be called before the regular "after_request" + # functions. self._after_request_functions = [] self.match_request() From d5218997d927be869dd55ef04542e1bbc1e69653 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Wed, 27 Jun 2012 15:06:39 +0100 Subject: [PATCH 0196/2940] Added flask.stream_with_context --- CHANGES | 2 + docs/api.rst | 5 +++ docs/patterns/streaming.rst | 23 ++++++++++++ flask/__init__.py | 3 +- flask/ctx.py | 64 +++++++++++++++++++------------- flask/helpers.py | 73 +++++++++++++++++++++++++++++++++++++ flask/testsuite/appctx.py | 20 ++++++++++ flask/testsuite/helpers.py | 59 ++++++++++++++++++++++++++++++ 8 files changed, 222 insertions(+), 27 deletions(-) diff --git a/CHANGES b/CHANGES index 28a0790c..acdfee69 100644 --- a/CHANGES +++ b/CHANGES @@ -71,6 +71,8 @@ Relase date to be decided, codename to be chosen. - Added `required_methods` attribute to view functions to force-add methods on registration. - Added :func:`flask.after_this_request`. +- Added :func:`flask.stream_with_context` and the ability to push contexts + multiple times without producing unexpected behavior. Version 0.8.1 ------------- diff --git a/docs/api.rst b/docs/api.rst index dcb54baf..8a7b5ce0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -375,6 +375,11 @@ Extensions .. versionadded:: 0.8 +Stream Helpers +-------------- + +.. autofunction:: stream_with_context + Useful Internals ---------------- diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index 8393b00b..ac232dcc 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -59,3 +59,26 @@ The template is then evaluated as the stream is iterated over. Since each time you do a yield the server will flush the content to the client you might want to buffer up a few items in the template which you can do with ``rv.enable_buffering(size)``. ``5`` is a sane default. + +Streaming with Context +---------------------- + +.. versionadded:: 0.9 + +Note that when you stream data, the request context is already gone the +moment the function executes. Flask 0.9 provides you with a helper that +can keep the request context around during the execution of the +generator:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + +Without the :func:`~flask.stream_with_context` function you would get a +:class:`RuntimeError` at that point. diff --git a/flask/__init__.py b/flask/__init__.py index de84bb69..e48f7a97 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -22,7 +22,8 @@ from .app import Flask, Request, Response from .config import Config from .helpers import url_for, jsonify, json_available, flash, \ send_file, send_from_directory, get_flashed_messages, \ - get_template_attribute, make_response, safe_join + get_template_attribute, make_response, safe_join, \ + stream_with_context from .globals import current_app, g, request, session, _request_ctx_stack, \ _app_ctx_stack from .ctx import has_request_context, has_app_context, \ diff --git a/flask/ctx.py b/flask/ctx.py index 0cf34491..3ea42a27 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -22,14 +22,6 @@ class _RequestGlobals(object): pass -def _push_app_if_necessary(app): - top = _app_ctx_stack.top - if top is None or top.app != app: - ctx = app.app_context() - ctx.push() - return ctx - - def after_this_request(f): """Executes a function after this request. This is useful to modify response objects. The function is passed the response object and has @@ -110,15 +102,22 @@ class AppContext(object): self.app = app self.url_adapter = app.create_url_adapter(None) + # Like request context, app contexts can be pushed multiple times + # but there a basic "refcount" is enough to track them. + self._refcnt = 0 + def push(self): """Binds the app context to the current context.""" + self._refcnt += 1 _app_ctx_stack.push(self) def pop(self, exc=None): """Pops the app context.""" - if exc is None: - exc = sys.exc_info()[1] - self.app.do_teardown_appcontext(exc) + self._refcnt -= 1 + if self._refcnt <= 0: + if exc is None: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) rv = _app_ctx_stack.pop() assert rv is self, 'Popped wrong app context. (%r instead of %r)' \ % (rv, self) @@ -128,7 +127,7 @@ class AppContext(object): return self def __exit__(self, exc_type, exc_value, tb): - self.pop() + self.pop(exc_value) class RequestContext(object): @@ -169,15 +168,16 @@ class RequestContext(object): self.flashes = None self.session = None + # Request contexts can be pushed multiple times and interleaved with + # other request contexts. Now only if the last level is popped we + # get rid of them. Additionally if an application context is missing + # one is created implicitly so for each level we add this information + self._implicit_app_ctx_stack = [] + # indicator if the context was preserved. Next time another context # is pushed the preserved context is popped. self.preserved = False - # Indicates if pushing this request context also triggered the pushing - # of an application context. If it implicitly pushed an application - # context, it will be stored there - self._pushed_application_context = None - # Functions that should be executed after the request on the response # object. These will be called before the regular "after_request" # functions. @@ -222,7 +222,13 @@ class RequestContext(object): # Before we push the request context we have to ensure that there # is an application context. - self._pushed_application_context = _push_app_if_necessary(self.app) + app_ctx = _app_ctx_stack.top + if app_ctx is None or app_ctx.app != self.app: + app_ctx = self.app.app_context() + app_ctx.push() + self._implicit_app_ctx_stack.append(app_ctx) + else: + self._implicit_app_ctx_stack.append(None) _request_ctx_stack.push(self) @@ -241,22 +247,28 @@ class RequestContext(object): .. versionchanged:: 0.9 Added the `exc` argument. """ - self.preserved = False - if exc is None: - exc = sys.exc_info()[1] - self.app.do_teardown_request(exc) + app_ctx = self._implicit_app_ctx_stack.pop() + + clear_request = False + if not self._implicit_app_ctx_stack: + self.preserved = False + if exc is None: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + clear_request = True + rv = _request_ctx_stack.pop() assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ % (rv, self) # get rid of circular dependencies at the end of the request # so that we don't require the GC to be active. - rv.request.environ['werkzeug.request'] = None + if clear_request: + rv.request.environ['werkzeug.request'] = None # Get rid of the app as well if necessary. - if self._pushed_application_context: - self._pushed_application_context.pop(exc) - self._pushed_application_context = None + if app_ctx is not None: + app_ctx.pop(exc) def __enter__(self): self.push() diff --git a/flask/helpers.py b/flask/helpers.py index 631e29be..501a2f81 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -21,6 +21,7 @@ from zlib import adler32 from threading import RLock from werkzeug.routing import BuildError from werkzeug.urls import url_quote +from functools import update_wrapper # try to load the best simplejson implementation available. If JSON # is not installed, we add a failing class. @@ -92,6 +93,78 @@ def _endpoint_from_view_func(view_func): return view_func.__name__ +def stream_with_context(generator_or_function): + """Request contexts disappear when the response is started on the server. + This is done for efficiency reasons and to make it less likely to encounter + memory leaks with badly written WSGI middlewares. The downside is that if + you are using streamed responses, the generator cannot access request bound + information any more. + + This function however can help you keep the context around for longer:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + @stream_with_context + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(generate()) + + Alternatively it can also be used around a specific generator: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + gen = iter(generator_or_function) + except TypeError: + def decorator(*args, **kwargs): + gen = generator_or_function() + return stream_with_context(gen) + return update_wrapper(decorator, generator_or_function) + + def generator(): + ctx = _request_ctx_stack.top + if ctx is None: + raise RuntimeError('Attempted to stream with context but ' + 'there was no context in the first place to keep around.') + with ctx: + # Dummy sentinel. Has to be inside the context block or we're + # not actually keeping the context around. + yield None + + # The try/finally is here so that if someone passes a WSGI level + # iterator in we're still running the cleanup logic. Generators + # don't need that because they are closed on their destruction + # automatically. + try: + for item in gen: + yield item + finally: + if hasattr(gen, 'close'): + gen.close() + + # The trick is to start the generator. Then the code execution runs until + # the first dummy None is yielded at which point the context was already + # pushed. This item is discarded. Then when the iteration continues the + # real generator is executed. + wrapped_g = generator() + wrapped_g.next() + return wrapped_g + + def jsonify(*args, **kwargs): """Creates a :class:`~flask.Response` with the JSON representation of the given arguments with an `application/json` mimetype. The arguments diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index 1dcdb406..6454389e 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -75,6 +75,26 @@ class AppContextTestCase(FlaskTestCase): self.assert_equal( flask.render_template_string('{{ g.spam }}'), 'eggs') + def test_context_refcounts(self): + called = [] + app = flask.Flask(__name__) + @app.teardown_request + def teardown_req(error=None): + called.append('request') + @app.teardown_appcontext + def teardown_app(error=None): + called.append('app') + @app.route('/') + def index(): + with flask._app_ctx_stack.top: + with flask._request_ctx_stack.top: + pass + self.assert_(flask._request_ctx_stack.request.environ + ['werkzeug.request'] is not None) + c = app.test_client() + c.get('/') + self.assertEqual(called, ['request', 'app']) + def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 816f6cd8..54c01482 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -397,6 +397,64 @@ class NoImportsTestCase(FlaskTestCase): self.fail('Flask(import_name) is importing import_name.') +class StreamingTestCase(FlaskTestCase): + + def test_streaming_with_context(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + def generate(): + yield 'Hello ' + yield flask.request.args['name'] + yield '!' + return flask.Response(flask.stream_with_context(generate())) + c = app.test_client() + rv = c.get('/?name=World') + self.assertEqual(rv.data, 'Hello World!') + + def test_streaming_with_context_as_decorator(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + @flask.stream_with_context + def generate(): + yield 'Hello ' + yield flask.request.args['name'] + yield '!' + return flask.Response(generate()) + c = app.test_client() + rv = c.get('/?name=World') + self.assertEqual(rv.data, 'Hello World!') + + def test_streaming_with_context_and_custom_close(self): + app = flask.Flask(__name__) + app.testing = True + called = [] + class Wrapper(object): + def __init__(self, gen): + self._gen = gen + def __iter__(self): + return self + def close(self): + called.append(42) + def next(self): + return self._gen.next() + @app.route('/') + def index(): + def generate(): + yield 'Hello ' + yield flask.request.args['name'] + yield '!' + return flask.Response(flask.stream_with_context( + Wrapper(generate()))) + c = app.test_client() + rv = c.get('/?name=World') + self.assertEqual(rv.data, 'Hello World!') + self.assertEqual(called, [42]) + + def suite(): suite = unittest.TestSuite() if flask.json_available: @@ -404,4 +462,5 @@ def suite(): suite.addTest(unittest.makeSuite(SendfileTestCase)) suite.addTest(unittest.makeSuite(LoggingTestCase)) suite.addTest(unittest.makeSuite(NoImportsTestCase)) + suite.addTest(unittest.makeSuite(StreamingTestCase)) return suite From ee3e251f9eb557721517faa6d06a6addd48ebc24 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 1 Jul 2012 12:12:36 +0100 Subject: [PATCH 0197/2940] Updated CHANGES --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 68ddab78..6d0c4925 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. Version 0.9 ----------- -Relase date to be decided, codename to be chosen. +Released on July 1st 2012, codename Camapri. - The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted response by default. From d4415dd6653adb25b89b6276dd140141266ba46b Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 1 Jul 2012 12:26:45 +0100 Subject: [PATCH 0198/2940] Fixed an rst syntax error --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 501a2f81..bc40428d 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -113,7 +113,7 @@ def stream_with_context(generator_or_function): yield '!' return Response(generate()) - Alternatively it can also be used around a specific generator: + Alternatively it can also be used around a specific generator:: from flask import stream_with_context, request, Response From 56f5224ef7cdc48a05b4ce6dcc37043feab0c0bb Mon Sep 17 00:00:00 2001 From: Laurens Van Houtven <_@lvh.cc> Date: Sun, 1 Jul 2012 19:31:53 +0300 Subject: [PATCH 0199/2940] CHANGES: July 1th should be July 1st --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6d0c4925..5889aa92 100644 --- a/CHANGES +++ b/CHANGES @@ -77,7 +77,7 @@ Released on July 1st 2012, codename Camapri. Version 0.8.1 ------------- -Bugfix release, released on July 1th 2012 +Bugfix release, released on July 1st 2012 - Fixed an issue with the undocumented `flask.session` module to not work properly on Python 2.5. It should not be used but did cause From d8e5a37d8a5e9dda1154c6c7c614bb7a4c42afdd Mon Sep 17 00:00:00 2001 From: esaurito <metallourlante@gmail.com> Date: Mon, 2 Jul 2012 00:38:27 +0300 Subject: [PATCH 0200/2940] Fixed codename --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5889aa92..7d1aeca1 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. Version 0.9 ----------- -Released on July 1st 2012, codename Camapri. +Released on July 1st 2012, codename Campari. - The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted response by default. From 1beb0f2e7f8c60df41cd7b198c1ed1748a0fea3d Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 2 Jul 2012 09:11:24 +0100 Subject: [PATCH 0201/2940] Fixed a syntax error --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 501a2f81..bc40428d 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -113,7 +113,7 @@ def stream_with_context(generator_or_function): yield '!' return Response(generate()) - Alternatively it can also be used around a specific generator: + Alternatively it can also be used around a specific generator:: from flask import stream_with_context, request, Response From 0553bbdefe55709a1fc36dce445f552f398a428e Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase <sh@lutzhaase.com> Date: Wed, 4 Jul 2012 21:12:41 +0200 Subject: [PATCH 0202/2940] Made it explicitly clear where changes should go in the tutorial --- docs/tutorial/dbinit.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index b546a1a8..15a107cf 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -23,14 +23,17 @@ for you to the application. If you want to do that, you first have to import the :func:`contextlib.closing` function from the contextlib package. If you want to use Python 2.5 it's also necessary to enable the `with` statement -first (`__future__` imports must be the very first import):: +first (`__future__` imports must be the very first import). Accordingly, +the following lines should be to your existing import lines in +`flaskr.py`:: from __future__ import with_statement from contextlib import closing Next we can create a function called `init_db` that initializes the database. For this we can use the `connect_db` function we defined -earlier. Just add that function below the `connect_db` function:: +earlier. Just add that function below the `connect_db` function in +`flask.py`:: def init_db(): with closing(connect_db()) as db: From 690b0c34ff3a9160aad16c613f29b1a8e25ca232 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase <sh@lutzhaase.com> Date: Wed, 4 Jul 2012 21:19:41 +0200 Subject: [PATCH 0203/2940] Fix chose -> choose typo --- docs/tutorial/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index c72bbd7d..1dcc40ed 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -3,7 +3,7 @@ Introducing Flaskr ================== -We will call our blogging application flaskr here, feel free to chose a +We will call our blogging application flaskr here, feel free to choose a less web-2.0-ish name ;) Basically we want it to do the following things: 1. let the user sign in and out with credentials specified in the From ea2a0629c9fc3874319e98b3842365ffd889f43d Mon Sep 17 00:00:00 2001 From: Ron DuPlain <ron.duplain@gmail.com> Date: Wed, 4 Jul 2012 15:27:15 -0400 Subject: [PATCH 0204/2940] Touch up dbinit tutorial doc. Discussion on #pocoo with svenstaro, dAnjou, noob13. --- docs/tutorial/dbinit.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 15a107cf..79479397 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -24,8 +24,7 @@ If you want to do that, you first have to import the :func:`contextlib.closing` function from the contextlib package. If you want to use Python 2.5 it's also necessary to enable the `with` statement first (`__future__` imports must be the very first import). Accordingly, -the following lines should be to your existing import lines in -`flaskr.py`:: +add the following lines to your existing imports in `flaskr.py`:: from __future__ import with_statement from contextlib import closing From c3f651dccbba801fa07426cef1e6604b579b8fe7 Mon Sep 17 00:00:00 2001 From: Simon Sapin <simon.sapin@exyr.org> Date: Thu, 12 Jul 2012 16:31:53 +0300 Subject: [PATCH 0205/2940] Remove the unused `ScriptNameStripper.to_strip` in the FastCGI doc example. Alernatively, `environ['SCRIPT_NAME'] = ''` should be replaced with something like: if environ['SCRIPT_NAME'].startswith(self.to_strip): environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][len(self.to_strip):] --- docs/deploying/fastcgi.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index 0e2f6cdc..8bce7d74 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -95,8 +95,6 @@ Set yourapplication.fcgi:: from yourapplication import app class ScriptNameStripper(object): - to_strip = '/yourapplication.fcgi' - def __init__(self, app): self.app = app From b9df128ba86ec5bc5513bf38b5484f74866ff447 Mon Sep 17 00:00:00 2001 From: Akai Kitsune <open.standards.needed@gmail.com> Date: Mon, 16 Jul 2012 21:08:02 +0300 Subject: [PATCH 0206/2940] Added directions for mod_wsgi vhost configuration under Apache on Windows --- docs/deploying/mod_wsgi.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index c4cd3d61..8fd2c0bb 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -91,6 +91,20 @@ execute the application under a different user for security reasons: </Directory> </VirtualHost> +Note: WSGIDaemonProcess isn't implemented in Windows and Apache will +refuse to run with the above configuration. On a Windows system, eliminate those lines: + +.. sourcecode:: apache + + <VirtualHost *> + ServerName example.com + WSGIScriptAlias / C:\yourdir\yourapp.wsgi + <Directory C:\yourdir> + Order deny,allow + Allow from all + </Directory> + </VirtualHost> + For more information consult the `mod_wsgi wiki`_. .. _mod_wsgi: http://code.google.com/p/modwsgi/ From 40ccc0a99aa9c1f975bcc7f69d452afd46fc8b90 Mon Sep 17 00:00:00 2001 From: Randall Degges <rdegges@gmail.com> Date: Fri, 20 Jul 2012 14:29:10 -0700 Subject: [PATCH 0207/2940] Fixing some wording in the design documentation. --- docs/design.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design.rst b/docs/design.rst index cc247f3b..ee83840e 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -90,9 +90,9 @@ since decorators could be fired in undefined order when the application is split into multiple modules. Another design decision with the Werkzeug routing system is that routes -in Werkzeug try to ensure that there is that URLs are unique. Werkzeug -will go quite far with that in that it will automatically redirect to a -canonical URL if a route is ambiguous. +in Werkzeug try to ensure that URLs are unique. Werkzeug will go quite far +with that in that it will automatically redirect to a canonical URL if a route +is ambiguous. One Template Engine From 3800c7396b20d90ff3a84a3f26c9f12e7365a085 Mon Sep 17 00:00:00 2001 From: Ramiro Gomez <web@ramiro.org> Date: Sat, 21 Jul 2012 13:55:45 +0200 Subject: [PATCH 0208/2940] Try to correct confusing sentence in doc and fixed word duplication. --- docs/appcontext.rst | 3 +-- docs/quickstart.rst | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index e9e1ad8f..346cb09b 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -44,8 +44,7 @@ you can have more than one application in the same Python process. So how does the code find the “right” application? In the past we recommended passing applications around explicitly, but that caused issues -with libraries that were not designed with that in mind for libraries for -which it was too inconvenient to make this work. +with libraries that were not designed with that in mind. A common workaround for that problem was to use the :data:`~flask.current_app` proxy later on, which was bound to the current diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e8b71ca9..84ffb488 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -377,7 +377,7 @@ package it's actually inside your package: /hello.html For templates you can use the full power of Jinja2 templates. Head over -to the the official `Jinja2 Template Documentation +to the official `Jinja2 Template Documentation <http://jinja.pocoo.org/2/documentation/templates>`_ for more information. Here is an example template: From b0fdae4e1f45274e0b399523cca9b55627d9afde Mon Sep 17 00:00:00 2001 From: Sven Slootweg <jamsoftgamedev@gmail.com> Date: Sat, 21 Jul 2012 21:00:44 +0200 Subject: [PATCH 0209/2940] Fix regex in lighttpd example config to only match static/ and sub-items, and not all directories that start with 'static' --- docs/deploying/fastcgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index 0e2f6cdc..f6ba7cee 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -128,7 +128,7 @@ A basic FastCGI configuration for lighttpd looks like that:: ) url.rewrite-once = ( - "^(/static.*)$" => "$1", + "^(/static($|/.*))$" => "$1", "^(/.*)$" => "/yourapplication.fcgi$1" Remember to enable the FastCGI, alias and rewrite modules. This configuration From 20a542fc8ac6dadbcbac92e84fb554e00ccc897a Mon Sep 17 00:00:00 2001 From: Paul McMillan <paul.mcmillan@nebula.com> Date: Thu, 26 Jul 2012 09:56:01 -0700 Subject: [PATCH 0210/2940] docstring typo --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 8460f476..9cac4025 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1446,7 +1446,7 @@ class Flask(_PackageBoundObject): .. versionchanged:: 0.9 This can now also be called without a request object when the - UR adapter is created for the application context. + URL adapter is created for the application context. """ if request is not None: return self.url_map.bind_to_environ(request.environ, From ed1619adadc56641b8ab1ba2c03d45023960aaf2 Mon Sep 17 00:00:00 2001 From: Priit Laes <plaes@plaes.org> Date: Wed, 1 Aug 2012 11:27:28 +0300 Subject: [PATCH 0211/2940] Docs: Mention SERVER_NAME in the url_for() docstring --- flask/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 501a2f81..2e8d0da1 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -305,7 +305,9 @@ def url_for(endpoint, **values): :param endpoint: the endpoint of the URL (name of the function) :param values: the variable arguments of the URL rule - :param _external: if set to `True`, an absolute URL is generated. + :param _external: if set to `True`, an absolute URL is generated. Server + address can be changed via `SERVER_NAME` configuration variable which + defaults to `localhost`. :param _anchor: if provided this is added as anchor to the URL. :param _method: if provided this explicitly specifies an HTTP method. """ From e3b3e05052ea36456cb557b8799b7b0ed00865bd Mon Sep 17 00:00:00 2001 From: Priit Laes <plaes@plaes.org> Date: Wed, 1 Aug 2012 11:29:40 +0300 Subject: [PATCH 0212/2940] Docs: Fix docstring formatting --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 2e8d0da1..7e20c97d 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -113,7 +113,7 @@ def stream_with_context(generator_or_function): yield '!' return Response(generate()) - Alternatively it can also be used around a specific generator: + Alternatively it can also be used around a specific generator:: from flask import stream_with_context, request, Response From 4df3bf2058954624f9376fd16774a769299dc40a Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 02:36:14 +0100 Subject: [PATCH 0213/2940] Implemented experimental JSON based sessions --- CHANGES | 9 +++++ docs/api.rst | 7 ++++ docs/upgrading.rst | 51 +++++++++++++++++++++++++ flask/sessions.py | 80 +++++++++++++++++++++++++++++++++++++++- flask/testsuite/basic.py | 26 +++++++++++++ 5 files changed, 172 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6d0c4925..1948b5ab 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,15 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.10 +------------ + +Release date to be decided. + +- Changed default cookie serialization format from pickle to JSON to + limit the impact an attacker can do if the secret key leaks. See + :ref:`upgrading-to-010` for more information. + Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index 8a7b5ce0..e808e771 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -215,6 +215,13 @@ implementation that Flask is using. .. autoclass:: SecureCookieSessionInterface :members: +.. autoclass:: UpgradeSecureCookieSessionInterface + +.. autoclass:: SecureCookieSession + :members: + +.. autoclass:: UpgradeSecureCookieSession + .. autoclass:: NullSession :members: diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 7226d60e..01393983 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -19,6 +19,57 @@ installation, make sure to pass it the ``-U`` parameter:: $ easy_install -U Flask +.. _upgrading-to-010: + +Version 0.10 +------------ + +The biggest change going from 0.9 to 0.10 is that the cookie serialization +format changed from pickle to a specialized JSON format. This change has +been done in order to avoid the damage an attacker can do if the secret +key is leaked. When you upgrade you will notice two major changes: all +sessions that were issued before the upgrade are invalidated and you can +only store a limited amount of types in the session. There are two ways +to avoid these problems on upgrading: + +Automatically Upgrade Sessions +`````````````````````````````` + +The first method is to allow pickle based sessions for a limited amount of +time. This can be done by using the +:class:`~flask.sessions.UpgradeSecureCookieSession` session +implementation:: + + from flask import Flask + from flask.sessions import UpgradeSecureCookieSessionInterface + + app = Flask(__name__) + app.session_interface = UpgradeSecureCookieSessionInterface + +For as long as this class is being used both pickle and json sessions are +supported but changes are written in JSON format only. + +Revert to Pickle Sessions +````````````````````````` + +You can also revert to pickle based sessions if you want:: + + import pickle + from flask import Flask + from flask.sessions import SecureCookieSession, \ + SecureCookieSessionInterface + + class PickleSessionInterface(SecureCookieSessionInterface): + class session_class(SecureCookieSession): + serialization_method = pickle + + app = Flask(__name__) + app.session_interface = PickleSessionInterface + +If you want to continue to use pickle based data we strongly recommend +switching to a server side session store however. + + Version 0.9 ----------- diff --git a/flask/sessions.py b/flask/sessions.py index 2795bb1f..75f4a614 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -10,8 +10,12 @@ :license: BSD, see LICENSE for more details. """ +import cPickle as pickle from datetime import datetime from werkzeug.contrib.securecookie import SecureCookie +from werkzeug.http import http_date, parse_date +from .helpers import json, _assert_have_json +from . import Markup class SessionMixin(object): @@ -41,10 +45,74 @@ class SessionMixin(object): modified = True +class TaggedJSONSerializer(object): + """A customized JSON serializer that supports a few extra types that + we take for granted when serializing (tuples, markup objects, datetime). + """ + + def dumps(self, value): + if __debug__: + _assert_have_json() + def _tag(value): + if isinstance(value, tuple): + return {'##t': [_tag(x) for x in value]} + elif callable(getattr(value, '__html__', None)): + return {'##m': unicode(value.__html__())} + elif isinstance(value, list): + return [_tag(x) for x in value] + elif isinstance(value, datetime): + return {'##d': http_date(value)} + elif isinstance(value, dict): + return dict((k, _tag(v)) for k, v in value.iteritems()) + return value + return json.dumps(_tag(value), separators=(',', ':')) + + def loads(self, value): + if __debug__: + _assert_have_json() + def object_hook(obj): + if len(obj) != 1: + return obj + the_key, the_value = obj.iteritems().next() + if the_key == '##t': + return tuple(the_value) + elif the_key == '##m': + return Markup(the_value) + elif the_key == '##d': + return parse_date(the_value) + return obj + return json.loads(value, object_hook=object_hook) + + +session_json_serializer = TaggedJSONSerializer() + + class SecureCookieSession(SecureCookie, SessionMixin): """Expands the session with support for switching between permanent - and non-permanent sessions. + and non-permanent sessions and changes the default pickle based + serialization format to a tagged json one. """ + serialization_method = session_json_serializer + + +class _UpgradeSerializer(object): + def dumps(self, value): + return session_json_serializer.dumps(value) + def loads(self, value): + try: + return session_json_serializer.loads(value) + except Exception: + return pickle.loads(value) + + +class UpgradeSecureCookieSession(SecureCookieSession): + """This cookie sesion implementation tries json first but will also + support pickle based session. This exists mainly to upgrade existing + pickle based users transparently to json. + + .. versionadded:: 0.10 + """ + serialization_method = _UpgradeSerializer() class NullSession(SecureCookieSession): @@ -203,3 +271,13 @@ class SecureCookieSessionInterface(SessionInterface): session.save_cookie(response, app.session_cookie_name, path=path, expires=expires, httponly=httponly, secure=secure, domain=domain) + + +class UpgradeSecureCookieSessionInterface(SecureCookieSessionInterface): + """This session interface works exactly like the regular one but uses + the :class:`UpgradeSecureCookieSession` classes to upgrade from pickle + sessions to JSON sessions. + + .. versionadded:: 0.10 + """ + session_class = UpgradeSecureCookieSession diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 388b5a8e..3d758b3a 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -13,6 +13,7 @@ from __future__ import with_statement import re import flask +import pickle import unittest from datetime import datetime from threading import Thread @@ -297,6 +298,31 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(c.get('/').data, 'None') self.assert_equal(c.get('/').data, '42') + def test_session_special_types(self): + app = flask.Flask(__name__) + app.secret_key = 'development-key' + app.testing = True + now = datetime.utcnow().replace(microsecond=0) + + @app.after_request + def modify_session(response): + flask.session['m'] = flask.Markup('Hello!') + flask.session['dt'] = now + flask.session['t'] = (1, 2, 3) + return response + + @app.route('/') + def dump_session_contents(): + return pickle.dumps(dict(flask.session)) + + c = app.test_client() + c.get('/') + rv = pickle.loads(c.get('/').data) + self.assert_equal(rv['m'], flask.Markup('Hello!')) + self.assert_equal(type(rv['m']), flask.Markup) + self.assert_equal(rv['dt'], now) + self.assert_equal(rv['t'], (1, 2, 3)) + def test_flashes(self): app = flask.Flask(__name__) app.secret_key = 'testkey' From ee28dcf2cff53aae0c88db34ff872e0b042c6bf5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 02:36:29 +0100 Subject: [PATCH 0214/2940] Added changelog entry for 0.10 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 6d0c4925..625d22dd 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.10 +------------ + +Release date to be decided. + Version 0.9 ----------- From b87919348104774e9c959fc00976966e38161129 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 02:37:03 +0100 Subject: [PATCH 0215/2940] Set current dev version number to 0.10 --- flask/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index b170ba5f..196eb033 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.9' +__version__ = '0.10-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. diff --git a/setup.py b/setup.py index 9b185fc0..1d0761fe 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ class run_audit(Command): setup( name='Flask', - version='0.9', + version='0.10-dev', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 3f82d1b68ea6f5bf2970c2df8ff5cf991439a9bf Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 03:09:14 +0100 Subject: [PATCH 0216/2940] Switch to itsdangerous --- flask/sessions.py | 100 +++++++++++++++++++--------------------------- setup.py | 3 +- 2 files changed, 44 insertions(+), 59 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 75f4a614..3db1fb60 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -10,13 +10,14 @@ :license: BSD, see LICENSE for more details. """ -import cPickle as pickle from datetime import datetime -from werkzeug.contrib.securecookie import SecureCookie from werkzeug.http import http_date, parse_date -from .helpers import json, _assert_have_json +from werkzeug.datastructures import CallbackDict +from .helpers import json from . import Markup +from itsdangerous import URLSafeTimedSerializer, BadSignature + class SessionMixin(object): """Expands a basic dictionary with an accessors that are expected @@ -51,8 +52,6 @@ class TaggedJSONSerializer(object): """ def dumps(self, value): - if __debug__: - _assert_have_json() def _tag(value): if isinstance(value, tuple): return {'##t': [_tag(x) for x in value]} @@ -68,8 +67,6 @@ class TaggedJSONSerializer(object): return json.dumps(_tag(value), separators=(',', ':')) def loads(self, value): - if __debug__: - _assert_have_json() def object_hook(obj): if len(obj) != 1: return obj @@ -87,32 +84,14 @@ class TaggedJSONSerializer(object): session_json_serializer = TaggedJSONSerializer() -class SecureCookieSession(SecureCookie, SessionMixin): - """Expands the session with support for switching between permanent - and non-permanent sessions and changes the default pickle based - serialization format to a tagged json one. - """ - serialization_method = session_json_serializer +class SecureCookieSession(CallbackDict, SessionMixin): + """Baseclass for sessions based on signed cookies.""" - -class _UpgradeSerializer(object): - def dumps(self, value): - return session_json_serializer.dumps(value) - def loads(self, value): - try: - return session_json_serializer.loads(value) - except Exception: - return pickle.loads(value) - - -class UpgradeSecureCookieSession(SecureCookieSession): - """This cookie sesion implementation tries json first but will also - support pickle based session. This exists mainly to upgrade existing - pickle based users transparently to json. - - .. versionadded:: 0.10 - """ - serialization_method = _UpgradeSerializer() + def __init__(self, initial=None): + def on_update(self): + self.modified = True + CallbackDict.__init__(self, initial, on_update) + self.modified = False class NullSession(SecureCookieSession): @@ -246,38 +225,43 @@ class SessionInterface(object): class SecureCookieSessionInterface(SessionInterface): - """The cookie session interface that uses the Werkzeug securecookie - as client side session backend. - """ + salt = 'cookie-session' session_class = SecureCookieSession + serializer = session_json_serializer + + def get_serializer(self, app): + if not app.secret_key: + return None + return URLSafeTimedSerializer(app.secret_key, + salt=self.salt, + serializer=self.serializer) def open_session(self, app, request): - key = app.secret_key - if key is not None: - return self.session_class.load_cookie(request, - app.session_cookie_name, - secret_key=key) + s = self.get_serializer(app) + if s is None: + return None + val = request.cookies.get(app.session_cookie_name) + if not val: + return self.session_class() + max_age = app.permanent_session_lifetime.total_seconds() + try: + data = s.loads(val, max_age=max_age) + return self.session_class(data) + except BadSignature: + return self.session_class() def save_session(self, app, session, response): - expires = self.get_expiration_time(app, session) domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) - if session.modified and not session: - response.delete_cookie(app.session_cookie_name, path=path, - domain=domain) - else: - session.save_cookie(response, app.session_cookie_name, path=path, - expires=expires, httponly=httponly, - secure=secure, domain=domain) - - -class UpgradeSecureCookieSessionInterface(SecureCookieSessionInterface): - """This session interface works exactly like the regular one but uses - the :class:`UpgradeSecureCookieSession` classes to upgrade from pickle - sessions to JSON sessions. - - .. versionadded:: 0.10 - """ - session_class = UpgradeSecureCookieSession + if not session: + if session.modified: + response.delete_cookie(app.session_cookie_name, + domain=domain, path=path) + return + expires = self.get_expiration_time(app, session) + val = self.get_serializer(app).dumps(dict(session)) + response.set_cookie(app.session_cookie_name, val, + expires=expires, httponly=httponly, + domain=domain, path=path, secure=secure) diff --git a/setup.py b/setup.py index 1d0761fe..0225e5fc 100644 --- a/setup.py +++ b/setup.py @@ -91,7 +91,8 @@ setup( platforms='any', install_requires=[ 'Werkzeug>=0.7', - 'Jinja2>=2.4' + 'Jinja2>=2.4', + 'itsdangerous>=0.16' ], classifiers=[ 'Development Status :: 4 - Beta', From c3d38a21c664440fb8284aaaf8fbcce4a1c0849f Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 03:11:40 +0100 Subject: [PATCH 0217/2940] Removed json_available hack --- flask/__init__.py | 6 +++--- flask/helpers.py | 31 ++++--------------------------- flask/wrappers.py | 4 +--- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index 196eb033..ac1a3a00 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -20,7 +20,7 @@ from jinja2 import Markup, escape from .app import Flask, Request, Response from .config import Config -from .helpers import url_for, jsonify, json_available, flash, \ +from .helpers import url_for, jsonify, flash, \ send_file, send_from_directory, get_flashed_messages, \ get_template_attribute, make_response, safe_join, \ stream_with_context @@ -37,8 +37,8 @@ from .signals import signals_available, template_rendered, request_started, \ request_finished, got_request_exception, request_tearing_down # only import json if it's available -if json_available: - from .helpers import json +from .helpers import json # backwards compat, goes away in 1.0 from .sessions import SecureCookieSession as Session +json_available = True diff --git a/flask/helpers.py b/flask/helpers.py index 7e20c97d..b2d34a71 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -23,21 +23,9 @@ from werkzeug.routing import BuildError from werkzeug.urls import url_quote from functools import update_wrapper -# try to load the best simplejson implementation available. If JSON -# is not installed, we add a failing class. -json_available = True -json = None -try: - import simplejson as json -except ImportError: - try: - import json - except ImportError: - try: - # Google Appengine offers simplejson via django - from django.utils import simplejson as json - except ImportError: - json_available = False +# Use the same json implementation as itsdangerous on which we +# depend anyways. +from itsdangerous import simplejson as json from werkzeug.datastructures import Headers @@ -55,19 +43,10 @@ from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request -def _assert_have_json(): - """Helper function that fails if JSON is unavailable.""" - if not json_available: - raise RuntimeError('simplejson not installed') - - # figure out if simplejson escapes slashes. This behavior was changed # from one version to another without reason. -if not json_available or '\\/' not in json.dumps('/'): - +if '\\/' not in json.dumps('/'): def _tojson_filter(*args, **kwargs): - if __debug__: - _assert_have_json() return json.dumps(*args, **kwargs).replace('/', '\\/') else: _tojson_filter = json.dumps @@ -192,8 +171,6 @@ def jsonify(*args, **kwargs): .. versionadded:: 0.2 """ - if __debug__: - _assert_have_json() return current_app.response_class(json.dumps(dict(*args, **kwargs), indent=None if request.is_xhr else 2), mimetype='application/json') diff --git a/flask/wrappers.py b/flask/wrappers.py index 3ee718ff..060ee25b 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -14,7 +14,7 @@ from werkzeug.utils import cached_property from .exceptions import JSONBadRequest from .debughelpers import attach_enctype_error_multidict -from .helpers import json, _assert_have_json +from .helpers import json from .globals import _request_ctx_stack @@ -95,8 +95,6 @@ class Request(RequestBase): This requires Python 2.6 or an installed version of simplejson. """ - if __debug__: - _assert_have_json() if self.mimetype == 'application/json': request_charset = self.mimetype_params.get('charset') try: From a4977cfe2b57218579ca224af7cfea0864e6665b Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 03:13:16 +0100 Subject: [PATCH 0218/2940] Removed outdated section in the docs --- docs/api.rst | 4 ---- docs/upgrading.rst | 41 ++--------------------------------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e808e771..316c76ab 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -215,13 +215,9 @@ implementation that Flask is using. .. autoclass:: SecureCookieSessionInterface :members: -.. autoclass:: UpgradeSecureCookieSessionInterface - .. autoclass:: SecureCookieSession :members: -.. autoclass:: UpgradeSecureCookieSession - .. autoclass:: NullSession :members: diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 01393983..c295fb1c 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -29,46 +29,9 @@ format changed from pickle to a specialized JSON format. This change has been done in order to avoid the damage an attacker can do if the secret key is leaked. When you upgrade you will notice two major changes: all sessions that were issued before the upgrade are invalidated and you can -only store a limited amount of types in the session. There are two ways -to avoid these problems on upgrading: - -Automatically Upgrade Sessions -`````````````````````````````` - -The first method is to allow pickle based sessions for a limited amount of -time. This can be done by using the -:class:`~flask.sessions.UpgradeSecureCookieSession` session -implementation:: - - from flask import Flask - from flask.sessions import UpgradeSecureCookieSessionInterface - - app = Flask(__name__) - app.session_interface = UpgradeSecureCookieSessionInterface - -For as long as this class is being used both pickle and json sessions are -supported but changes are written in JSON format only. - -Revert to Pickle Sessions -````````````````````````` - -You can also revert to pickle based sessions if you want:: - - import pickle - from flask import Flask - from flask.sessions import SecureCookieSession, \ - SecureCookieSessionInterface - - class PickleSessionInterface(SecureCookieSessionInterface): - class session_class(SecureCookieSession): - serialization_method = pickle - - app = Flask(__name__) - app.session_interface = PickleSessionInterface - -If you want to continue to use pickle based data we strongly recommend -switching to a server side session store however. +only store a limited amount of types in the session. +TODO: add external module for session upgrading Version 0.9 ----------- From fe85970665ea3a38f9c6a8ef4756ff3a913850b6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sat, 11 Aug 2012 03:38:46 +0100 Subject: [PATCH 0219/2940] Various improvements in regards to the itsdangerous usage, bumped to 0.17 --- flask/sessions.py | 55 ++++++++++++++++++++++++++++++++++------------- setup.py | 2 +- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 3db1fb60..ba0b0ed7 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -10,6 +10,7 @@ :license: BSD, see LICENSE for more details. """ +import hashlib from datetime import datetime from werkzeug.http import http_date, parse_date from werkzeug.datastructures import CallbackDict @@ -54,13 +55,13 @@ class TaggedJSONSerializer(object): def dumps(self, value): def _tag(value): if isinstance(value, tuple): - return {'##t': [_tag(x) for x in value]} + return {' t': [_tag(x) for x in value]} elif callable(getattr(value, '__html__', None)): - return {'##m': unicode(value.__html__())} + return {' m': unicode(value.__html__())} elif isinstance(value, list): return [_tag(x) for x in value] elif isinstance(value, datetime): - return {'##d': http_date(value)} + return {' d': http_date(value)} elif isinstance(value, dict): return dict((k, _tag(v)) for k, v in value.iteritems()) return value @@ -71,11 +72,11 @@ class TaggedJSONSerializer(object): if len(obj) != 1: return obj the_key, the_value = obj.iteritems().next() - if the_key == '##t': + if the_key == ' t': return tuple(the_value) - elif the_key == '##m': + elif the_key == ' m': return Markup(the_value) - elif the_key == '##d': + elif the_key == ' d': return parse_date(the_value) return obj return json.loads(value, object_hook=object_hook) @@ -145,6 +146,13 @@ class SessionInterface(object): #: this type. null_session_class = NullSession + #: A flag that indicates if the session interface is pickle based. + #: This can be used by flask extensions to make a decision in regards + #: to how to deal with the session object. + #: + #: .. versionadded:: 0.10 + pickle_based = False + def make_null_session(self, app): """Creates a null session which acts as a replacement object if the real session support could not be loaded due to a configuration @@ -225,19 +233,36 @@ class SessionInterface(object): class SecureCookieSessionInterface(SessionInterface): + """The default session interface that stores sessions in signed cookies + through the :mod:`itsdangerous` module. + """ + #: the salt that should be applied on top of the secret key for the + #: signing of cookie based sessions. salt = 'cookie-session' - session_class = SecureCookieSession + #: the hash function to use for the signature. The default is sha1 + digest_method = staticmethod(hashlib.sha1) + #: the name of the itsdangerous supported key derivation. The default + #: is hmac. + key_derivation = 'hmac' + #: A python serializer for the payload. The default is a compact + #: JSON derived serializer with support for some extra Python types + #: such as datetime objects or tuples. serializer = session_json_serializer + session_class = SecureCookieSession - def get_serializer(self, app): + def get_signing_serializer(self, app): if not app.secret_key: return None - return URLSafeTimedSerializer(app.secret_key, - salt=self.salt, - serializer=self.serializer) + signer_kwargs = dict( + key_derivation=self.key_derivation, + digest_method=self.digest_method + ) + return URLSafeTimedSerializer(app.secret_key, salt=self.salt, + serializer=self.serializer, + signer_kwargs=signer_kwargs) def open_session(self, app, request): - s = self.get_serializer(app) + s = self.get_signing_serializer(app) if s is None: return None val = request.cookies.get(app.session_cookie_name) @@ -253,15 +278,15 @@ class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) - httponly = self.get_cookie_httponly(app) - secure = self.get_cookie_secure(app) if not session: if session.modified: response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return + httponly = self.get_cookie_httponly(app) + secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) - val = self.get_serializer(app).dumps(dict(session)) + val = self.get_signing_serializer(app).dumps(dict(session)) response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure) diff --git a/setup.py b/setup.py index 0225e5fc..4a9182b9 100644 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ setup( install_requires=[ 'Werkzeug>=0.7', 'Jinja2>=2.4', - 'itsdangerous>=0.16' + 'itsdangerous>=0.17' ], classifiers=[ 'Development Status :: 4 - Beta', From 77c2c3b183cf54f4cd678ccb4545246ea33fc52b Mon Sep 17 00:00:00 2001 From: Ben Rousch <brousch@gmail.com> Date: Fri, 17 Aug 2012 16:45:04 -0300 Subject: [PATCH 0220/2940] Added _ to fix link to extensions in Hook. Extend. I missed the trailing _ to make a link in my first patch to add the link to extensions. Sorry about that. --- docs/becomingbig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst index 8d531620..62f456bd 100644 --- a/docs/becomingbig.rst +++ b/docs/becomingbig.rst @@ -26,7 +26,7 @@ response objects. Dig deeper on the APIs you use, and look for the customizations which are available out of the box in a Flask release. Look for ways in which your project can be refactored into a collection of utilities and Flask extensions. Explore the many `extensions -<http://flask.pocoo.org/extensions/>` in the community, and look for patterns to +<http://flask.pocoo.org/extensions/>`_ in the community, and look for patterns to build your own extensions if you do not find the tools you need. Subclass. From 11c746189dd8b49740a833d0e8428a85b6174799 Mon Sep 17 00:00:00 2001 From: Alex Morega <alex@grep.ro> Date: Mon, 20 Aug 2012 16:57:18 +0300 Subject: [PATCH 0221/2940] Fix code example in pluggable views documentation --- docs/views.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/views.rst b/docs/views.rst index 02c62704..210d8f18 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -36,7 +36,7 @@ based view you would do this:: users = User.query.all() return render_template('users.html', objects=users) - app.add_url_rule('/users/', ShowUsers.as_view('show_users')) + app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users')) As you can see what you have to do is to create a subclass of :class:`flask.views.View` and implement From 8d330a368a7f0aa6ba8508cb2b4b64f6415a0c7b Mon Sep 17 00:00:00 2001 From: d1ffuz0r <d1fffuz0r@gmail.com> Date: Wed, 29 Aug 2012 22:26:39 +0400 Subject: [PATCH 0222/2940] fixed issue #524 --- scripts/flaskext_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index cb0b436c..050fd7e0 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -44,7 +44,7 @@ class ExtensionImporter(object): def install(self): sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] - def find_module(self, fullname, path=None): + def find_module(self, fullname): if fullname.startswith(self.prefix): return self From 69e419e020ea731a5bf4d4e95a6227761f01bcc7 Mon Sep 17 00:00:00 2001 From: pinchsp <spappier@gmail.com> Date: Tue, 4 Sep 2012 00:51:45 -0300 Subject: [PATCH 0223/2940] Fixed small mistake in sqlalchemy pattern --- docs/patterns/sqlalchemy.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 270fa79e..c6200187 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -42,7 +42,7 @@ Here the example `database.py` module for your application:: engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, - bind=engine)) + bind=engine)) Base = declarative_base() Base.query = db_session.query_property() @@ -130,7 +130,7 @@ Here is an example `database.py` module for your application:: metadata = MetaData() db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, - bind=engine)) + bind=engine)) def init_db(): metadata.create_all(bind=engine) @@ -189,7 +189,7 @@ To insert data you can use the `insert` method. We have to get a connection first so that we can use a transaction: >>> con = engine.connect() ->>> con.execute(users.insert(name='admin', email='admin@localhost')) +>>> con.execute(users.insert(), name='admin', email='admin@localhost') SQLAlchemy will automatically commit for us. From 48f7cdd01699fdc4048b18d827fccfbf9f266c9f Mon Sep 17 00:00:00 2001 From: Finbarr O'Callaghan <finbarr.ocallaghan@gmail.com> Date: Thu, 6 Sep 2012 18:04:51 +0100 Subject: [PATCH 0224/2940] various typo fixes --- docs/appcontext.rst | 4 ++-- docs/upgrading.rst | 2 +- flask/app.py | 18 +++++++++--------- flask/exthook.py | 2 +- flask/testsuite/subclassing.py | 8 ++++---- flask/wrappers.py | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index 346cb09b..928ffc8a 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -37,7 +37,7 @@ context local. Purpose of the Application Context ---------------------------------- -The main reason for the application's context existance is that in the +The main reason for the application's context existence is that in the past a bunch of functionality was attached to the request context in lack of a better solution. Since one of the pillar's of Flask's design is that you can have more than one application in the same Python process. @@ -58,7 +58,7 @@ Creating an Application Context To make an application context there are two ways. The first one is the implicit one: whenever a request context is pushed, an application context will be created alongside if this is necessary. As a result of that, you -can ignore the existance of the application context unless you need it. +can ignore the existence of the application context unless you need it. The second way is the explicit way using the :meth:`~flask.Flask.app_context` method:: diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 7226d60e..f3e3b021 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -191,7 +191,7 @@ Manual Error Handler Attaching While it is still possible to attach error handlers to :attr:`Flask.error_handlers` it's discouraged to do so and in fact -deprecated. In generaly we no longer recommend custom error handler +deprecated. In generally we no longer recommend custom error handler attaching via assignments to the underlying dictionary due to the more complex internal handling to support arbitrary exception classes and blueprints. See :meth:`Flask.errorhandler` for more information. diff --git a/flask/app.py b/flask/app.py index f8526d5c..68767d03 100644 --- a/flask/app.py +++ b/flask/app.py @@ -541,8 +541,8 @@ class Flask(_PackageBoundObject): Here some examples:: app.logger.debug('A value for debugging') - app.logger.warning('A warning ocurred (%d apples)', 42) - app.logger.error('An error occoured') + app.logger.warning('A warning occurred (%d apples)', 42) + app.logger.error('An error occurred') .. versionadded:: 0.3 """ @@ -846,7 +846,7 @@ class Flask(_PackageBoundObject): first_registration = False if blueprint.name in self.blueprints: assert self.blueprints[blueprint.name] is blueprint, \ - 'A blueprint\'s name collision ocurred between %r and ' \ + 'A blueprint\'s name collision occurred between %r and ' \ '%r. Both share the same name "%s". Blueprints that ' \ 'are created on the fly need unique names.' % \ (blueprint, self.blueprints[blueprint.name], blueprint.name) @@ -1108,7 +1108,7 @@ class Flask(_PackageBoundObject): a new response object or the same (see :meth:`process_response`). As of Flask 0.7 this function might not be executed at the end of the - request in case an unhandled exception ocurred. + request in case an unhandled exception occurred. """ self.after_request_funcs.setdefault(None, []).append(f) return f @@ -1132,10 +1132,10 @@ class Flask(_PackageBoundObject): stack of active contexts. This becomes relevant if you are using such constructs in tests. - Generally teardown functions must take every necesary step to avoid + Generally teardown functions must take every necessary step to avoid that they will fail. If they do execute code that might fail they will have to surround the execution of these code by try/except - statements and log ocurring errors. + statements and log occurring errors. When a teardown function was called because of a exception it will be passed an error object. @@ -1265,7 +1265,7 @@ class Flask(_PackageBoundObject): def handle_exception(self, e): """Default exception handling that kicks in when an exception - occours that is not caught. In debug mode the exception will + occurs that is not caught. In debug mode the exception will be re-raised immediately, otherwise it is logged and the handler for a 500 internal server error is used. If no such handler exists, a default 500 internal server error message is displayed. @@ -1522,7 +1522,7 @@ class Flask(_PackageBoundObject): request handling is stopped. This also triggers the :meth:`url_value_processor` functions before - the actualy :meth:`before_request` functions are called. + the actually :meth:`before_request` functions are called. """ bp = _request_ctx_stack.top.request.blueprint @@ -1675,7 +1675,7 @@ class Flask(_PackageBoundObject): The behavior of the before and after request callbacks was changed under error conditions and a new callback was added that will always execute at the end of the request, independent on if an - error ocurred or not. See :ref:`callbacks-and-errors`. + error occurred or not. See :ref:`callbacks-and-errors`. :param environ: a WSGI environment :param start_response: a callable accepting a status code, diff --git a/flask/exthook.py b/flask/exthook.py index bb1deb29..26578f0f 100644 --- a/flask/exthook.py +++ b/flask/exthook.py @@ -110,7 +110,7 @@ class ExtensionImporter(object): if module_name == important_module: return True - # Some python verisons will will clean up modules so early that the + # Some python versions will will clean up modules so early that the # module name at that point is no longer set. Try guessing from # the filename then. filename = os.path.abspath(tb.tb_frame.f_code.co_filename) diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py index e56ad563..89aa9150 100644 --- a/flask/testsuite/subclassing.py +++ b/flask/testsuite/subclassing.py @@ -18,14 +18,14 @@ from flask.testsuite import FlaskTestCase class FlaskSubclassingTestCase(FlaskTestCase): - def test_supressed_exception_logging(self): - class SupressedFlask(flask.Flask): + def test_suppressed_exception_logging(self): + class SuppressedFlask(flask.Flask): def log_exception(self, exc_info): pass out = StringIO() - app = SupressedFlask(__name__) - app.logger_name = 'flask_tests/test_supressed_exception_logging' + app = SuppressedFlask(__name__) + app.logger_name = 'flask_tests/test_suppressed_exception_logging' app.logger.addHandler(StreamHandler(out)) @app.route('/') diff --git a/flask/wrappers.py b/flask/wrappers.py index 3ee718ff..50dfdc2a 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -108,7 +108,7 @@ class Request(RequestBase): def on_json_loading_failed(self, e): """Called if decoding of the JSON data failed. The return value of - this method is used by :attr:`json` when an error ocurred. The default + this method is used by :attr:`json` when an error occurred. The default implementation raises a :class:`JSONBadRequest`, which is a subclass of :class:`~werkzeug.exceptions.BadRequest` which sets the ``Content-Type`` to ``application/json`` and provides a JSON-formatted From e8d50a7abafff3306c5076f10d82b66224c0ebd4 Mon Sep 17 00:00:00 2001 From: Finbarr O'Callaghan <finbarr.ocallaghan@gmail.com> Date: Thu, 6 Sep 2012 18:19:50 +0100 Subject: [PATCH 0225/2940] fixed spelling but not the grammar! --- docs/upgrading.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index f3e3b021..77c78ae4 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -191,7 +191,7 @@ Manual Error Handler Attaching While it is still possible to attach error handlers to :attr:`Flask.error_handlers` it's discouraged to do so and in fact -deprecated. In generally we no longer recommend custom error handler +deprecated. In general we no longer recommend custom error handler attaching via assignments to the underlying dictionary due to the more complex internal handling to support arbitrary exception classes and blueprints. See :meth:`Flask.errorhandler` for more information. From e93447f25e5f0cf0c10c32daa10b02e23ba2bc1a Mon Sep 17 00:00:00 2001 From: Finbarr O'Callaghan <finbarr.ocallaghan@gmail.com> Date: Thu, 6 Sep 2012 18:30:41 +0100 Subject: [PATCH 0226/2940] actually to actual, again, fixed spelling, not grammar --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 68767d03..b47e5a69 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1522,7 +1522,7 @@ class Flask(_PackageBoundObject): request handling is stopped. This also triggers the :meth:`url_value_processor` functions before - the actually :meth:`before_request` functions are called. + the actual :meth:`before_request` functions are called. """ bp = _request_ctx_stack.top.request.blueprint From 9ecbd20286aebcd2040cf7463d5288726787c1f3 Mon Sep 17 00:00:00 2001 From: Ralph Bean <rbean@redhat.com> Date: Thu, 13 Sep 2012 15:16:38 -0300 Subject: [PATCH 0227/2940] Update flask/templating.py Fixed a typo in the docstring. --- flask/templating.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index c809a63f..e5d896cb 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -129,8 +129,8 @@ def render_template_string(source, **context): """Renders a template from the given template source string with the given context. - :param template_name: the sourcecode of the template to be - rendered + :param source: the sourcecode of the template to be + rendered :param context: the variables that should be available in the context of the template. """ From 22af78a96f98222c2292d71dd1914f5be56362a0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Fri, 21 Sep 2012 01:02:42 +0900 Subject: [PATCH 0228/2940] Removed outdated sentence in the testing docs --- docs/testing.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 1e00fe80..d4d9259c 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -270,10 +270,6 @@ happen. With Flask 0.4 this is possible by using the If you were to use just the :meth:`~flask.Flask.test_client` without the `with` block, the `assert` would fail with an error because `request` is no longer available (because you are trying to use it outside of the actual request). -However, keep in mind that any :meth:`~flask.Flask.after_request` functions -are already called at this point so your database connection and -everything involved is probably already closed down. - Accessing and Modifying Sessions -------------------------------- From 7233a3e0a223e8d426ff937f5c03d88636b0ade4 Mon Sep 17 00:00:00 2001 From: Ryan Macy <ryan@bitrot.io> Date: Mon, 1 Oct 2012 14:45:02 -0500 Subject: [PATCH 0229/2940] Fixed typo occours to occurs Fixed a typo in the docstring of handle_exception. Was occours, now occurs. --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index f8526d5c..08fc3248 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1265,7 +1265,7 @@ class Flask(_PackageBoundObject): def handle_exception(self, e): """Default exception handling that kicks in when an exception - occours that is not caught. In debug mode the exception will + occurs that is not caught. In debug mode the exception will be re-raised immediately, otherwise it is logged and the handler for a 500 internal server error is used. If no such handler exists, a default 500 internal server error message is displayed. From 639817b6212465264d9c24faf4e43cefa2e7ae50 Mon Sep 17 00:00:00 2001 From: jfinkels <jeffrey.finkelstein@gmail.com> Date: Fri, 5 Oct 2012 02:49:55 -0300 Subject: [PATCH 0230/2940] Update docs/quickstart.rst Removed incorrect syntax and simplified remaining sentence. --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 1d72e510..e9d6b388 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -627,9 +627,9 @@ For this also see :ref:`about-responses`. Redirects and Errors -------------------- -To redirect a user to somewhere else you can use the -:func:`~flask.redirect` function. To abort a request early with an error -code use the :func:`~flask.abort` function. Here an example how this works:: +To redirect a user to another endpoint, use the :func:`~flask.redirect` +function; to abort a request early with an error code, use the +:func:`~flask.abort` function:: from flask import abort, redirect, url_for From 261c4a6aee88361e0de5d86061d873bbad2cb3a9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 12:40:59 +0200 Subject: [PATCH 0231/2940] Updated documentation for the new sessions --- docs/upgrading.rst | 9 +++++++-- flask/sessions.py | 5 ++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index c295fb1c..34f54f42 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -29,9 +29,14 @@ format changed from pickle to a specialized JSON format. This change has been done in order to avoid the damage an attacker can do if the secret key is leaked. When you upgrade you will notice two major changes: all sessions that were issued before the upgrade are invalidated and you can -only store a limited amount of types in the session. +only store a limited amount of types in the session. The new sessions are +by design much more restricted to only allow JSON with a few small +extensions for tuples and strings with HTML markup. -TODO: add external module for session upgrading +In order to not break people's sessions it is possible to continue using +the old session system by using the `Flask-OldSessions_` extension. + +.. _Flask-OldSessions: http://packages.python.org/Flask-OldSessions/ Version 0.9 ----------- diff --git a/flask/sessions.py b/flask/sessions.py index ba0b0ed7..4790f73a 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -3,10 +3,9 @@ flask.sessions ~~~~~~~~~~~~~~ - Implements cookie based sessions based on Werkzeug's secure cookie - system. + Implements cookie based sessions based on itsdangerous. - :copyright: (c) 2011 by Armin Ronacher. + :copyright: (c) 2012 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ From 4f1cb4212376acbbe7ab14fb8caa8a5025b4659e Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 12:48:19 +0200 Subject: [PATCH 0232/2940] make_test_environ_builder when used with subdomains was not working correctly, now it uses urlparse module for detecting full URL and changing path and base_url correctly --- flask/testing.py | 6 +++++- flask/testsuite/testing.py | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/flask/testing.py b/flask/testing.py index 782b40f6..bdd3860f 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -15,6 +15,7 @@ from __future__ import with_statement from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack +from urlparse import urlparse def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): @@ -22,9 +23,12 @@ def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): http_host = app.config.get('SERVER_NAME') app_root = app.config.get('APPLICATION_ROOT') if base_url is None: - base_url = 'http://%s/' % (http_host or 'localhost') + url = urlparse(path) + base_url = 'http://%s/' % (url.netloc or http_host or 'localhost') if app_root: base_url += app_root.lstrip('/') + if url.netloc: + path = url.path return EnvironBuilder(path, base_url, *args, **kwargs) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 0e6feb60..92e3f267 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -198,7 +198,46 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(called, [None, None]) +class SubdomainTestCase(FlaskTestCase): + + def setUp(self): + self.app = flask.Flask(__name__) + self.app.config['SERVER_NAME'] = 'example.com' + self.client = self.app.test_client() + + self._ctx = self.app.test_request_context() + self._ctx.push() + + def tearDown(self): + if self._ctx is not None: + self._ctx.pop() + + def test_subdomain(self): + @self.app.route('/', subdomain='<company_id>') + def view(company_id): + return company_id + + url = flask.url_for('view', company_id='xxx') + response = self.client.get(url) + + self.assertEquals(200, response.status_code) + self.assertEquals('xxx', response.data) + + + def test_nosubdomain(self): + @self.app.route('/<company_id>') + def view(company_id): + return company_id + + url = flask.url_for('view', company_id='xxx') + response = self.client.get(url) + + self.assertEquals(200, response.status_code) + self.assertEquals('xxx', response.data) + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestToolsTestCase)) + suite.addTest(unittest.makeSuite(SubdomainTestCase)) return suite From f034d8d3451f590fca1badeac5da0230bed9b148 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 12:51:46 +0200 Subject: [PATCH 0233/2940] Add @template_test() decorator for creating custom jinja2 tests, like existing @template_filter() for filters. Fixes #332 --- flask/app.py | 38 ++++++ flask/blueprints.py | 28 +++++ flask/testsuite/blueprints.py | 116 ++++++++++++++++++- flask/testsuite/templates/template_test.html | 3 + flask/testsuite/templating.py | 91 +++++++++++++-- 5 files changed, 265 insertions(+), 11 deletions(-) create mode 100644 flask/testsuite/templates/template_test.html diff --git a/flask/app.py b/flask/app.py index 08fc3248..7940a349 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1086,6 +1086,44 @@ class Flask(_PackageBoundObject): """ self.jinja_env.filters[name or f.__name__] = f + @setupmethod + def template_test(self, name=None): + """A decorator that is used to register custom template test. + You can specify a name for the test, otherwise the function + name will be used. Example:: + + @app.template_test() + def is_prime(n): + if n == 2: + return True + for i in xrange(2, int(math.ceil(math.sqrt(n))) + 1): + if n % i == 0: + return False + return True + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + def decorator(f): + self.add_template_test(f, name=name) + return f + return decorator + + @setupmethod + def add_template_test(self, f, name=None): + """Register a custom template test. Works exactly like the + :meth:`template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + self.jinja_env.tests[name or f.__name__] = f + + @setupmethod def before_request(self, f): """Registers a function to run before each request.""" diff --git a/flask/blueprints.py b/flask/blueprints.py index 9c557028..7ce23bbc 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -209,6 +209,34 @@ class Blueprint(_PackageBoundObject): state.app.jinja_env.filters[name or f.__name__] = f self.record_once(register_template) + def app_template_test(self, name=None): + """Register a custom template test, available application wide. Like + :meth:`Flask.template_test` but for a blueprint. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + def decorator(f): + self.add_app_template_test(f, name=name) + return f + return decorator + + def add_app_template_test(self, f, name=None): + """Register a custom template test, available application wide. Like + :meth:`Flask.add_template_test` but for a blueprint. Works exactly + like the :meth:`app_template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + def register_template(state): + state.app.jinja_env.tests[name or f.__name__] = f + self.record_once(register_template) + def before_request(self, f): """Like :meth:`Flask.before_request` but for a blueprint. This function is only executed before each request that is handled by a function of diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index c9622121..ea047918 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -548,7 +548,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -559,7 +559,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_filter(my_reverse) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -570,7 +570,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -581,7 +581,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_filter(my_reverse, 'strrev') app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -650,6 +650,114 @@ class BlueprintTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.data, 'dcba') + def test_template_test(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_test() + def is_boolean(value): + return isinstance(value, bool) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('is_boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['is_boolean'], is_boolean) + self.assert_(app.jinja_env.tests['is_boolean'](False)) + + def test_add_template_test(self): + bp = flask.Blueprint('bp', __name__) + def is_boolean(value): + return isinstance(value, bool) + bp.add_app_template_test(is_boolean) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('is_boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['is_boolean'], is_boolean) + self.assert_(app.jinja_env.tests['is_boolean'](False)) + + def test_template_test_with_name(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_test('boolean') + def is_boolean(value): + return isinstance(value, bool) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) + self.assert_(app.jinja_env.tests['boolean'](False)) + + def test_add_template_test_with_name(self): + bp = flask.Blueprint('bp', __name__) + def is_boolean(value): + return isinstance(value, bool) + bp.add_app_template_test(is_boolean, 'boolean') + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) + self.assert_(app.jinja_env.tests['boolean'](False)) + + def test_template_test_with_template(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_template_test_after_route_with_template(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + bp = flask.Blueprint('bp', __name__) + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + app.register_blueprint(bp, url_prefix='/py') + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_add_template_test_with_template(self): + bp = flask.Blueprint('bp', __name__) + def boolean(value): + return isinstance(value, bool) + bp.add_app_template_test(boolean) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_template_test_with_name_and_template(self): + bp = flask.Blueprint('bp', __name__) + @bp.app_template_test('boolean') + def is_boolean(value): + return isinstance(value, bool) + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_add_template_test_with_name_and_template(self): + bp = flask.Blueprint('bp', __name__) + def is_boolean(value): + return isinstance(value, bool) + bp.add_app_template_test(is_boolean, 'boolean') + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/templates/template_test.html b/flask/testsuite/templates/template_test.html new file mode 100644 index 00000000..92d5561b --- /dev/null +++ b/flask/testsuite/templates/template_test.html @@ -0,0 +1,3 @@ +{% if value is boolean %} + Success! +{% endif %} diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 4a0ebdbc..1df4292d 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -89,7 +89,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter() def my_reverse(s): return s[::-1] - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -98,7 +98,7 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] app.add_template_filter(my_reverse) - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -107,7 +107,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter('strrev') def my_reverse(s): return s[::-1] - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -116,7 +116,7 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] app.add_template_filter(my_reverse, 'strrev') - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -164,6 +164,86 @@ class TemplatingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.data, 'dcba') + def test_template_test(self): + app = flask.Flask(__name__) + @app.template_test() + def boolean(value): + return isinstance(value, bool) + self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['boolean'], boolean) + self.assert_(app.jinja_env.tests['boolean'](False)) + + def test_add_template_test(self): + app = flask.Flask(__name__) + def boolean(value): + return isinstance(value, bool) + app.add_template_test(boolean) + self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['boolean'], boolean) + self.assert_(app.jinja_env.tests['boolean'](False)) + + def test_template_test_with_name(self): + app = flask.Flask(__name__) + @app.template_test('boolean') + def is_boolean(value): + return isinstance(value, bool) + self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) + self.assert_(app.jinja_env.tests['boolean'](False)) + + def test_add_template_test_with_name(self): + app = flask.Flask(__name__) + def is_boolean(value): + return isinstance(value, bool) + app.add_template_test(is_boolean, 'boolean') + self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) + self.assert_(app.jinja_env.tests['boolean'](False)) + + def test_template_test_with_template(self): + app = flask.Flask(__name__) + @app.template_test() + def boolean(value): + return isinstance(value, bool) + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_add_template_test_with_template(self): + app = flask.Flask(__name__) + def boolean(value): + return isinstance(value, bool) + app.add_template_test(boolean) + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_template_test_with_name_and_template(self): + app = flask.Flask(__name__) + @app.template_test('boolean') + def is_boolean(value): + return isinstance(value, bool) + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + + def test_add_template_test_with_name_and_template(self): + app = flask.Flask(__name__) + def is_boolean(value): + return isinstance(value, bool) + app.add_template_test(is_boolean, 'boolean') + @app.route('/') + def index(): + return flask.render_template('template_test.html', value=False) + rv = app.test_client().get('/') + self.assert_('Success!' in rv.data) + def test_custom_template_loader(self): class MyFlask(flask.Flask): def create_global_jinja_loader(self): @@ -177,7 +257,6 @@ class TemplatingTestCase(FlaskTestCase): rv = c.get('/') self.assert_equal(rv.data, 'Hello Custom World!') - def test_iterable_loader(self): app = flask.Flask(__name__) @app.context_processor @@ -195,8 +274,6 @@ class TemplatingTestCase(FlaskTestCase): self.assert_equal(rv.data, '<h1>Jameson</h1>') - - def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TemplatingTestCase)) From c9a7fdf1b02ee79676d39bb614007bf922931a9c Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 12:53:36 +0200 Subject: [PATCH 0234/2940] Documented latest commit in changelog --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index a7c97cd7..be7ede79 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,8 @@ Release date to be decided. - Changed default cookie serialization format from pickle to JSON to limit the impact an attacker can do if the secret key leaks. See :ref:`upgrading-to-010` for more information. +- Added ``template_test`` methods in addition to the already existing + ``template_filter`` method family. Version 0.9 ----------- From 18413ed1bf08261acf6d40f8ba65a98ae586bb29 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 13:02:05 +0200 Subject: [PATCH 0235/2940] Added HTTP override middleware to docs. This fixes #582 --- docs/patterns/index.rst | 1 + docs/patterns/methodoverrides.rst | 43 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 docs/patterns/methodoverrides.rst diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 964b1e17..2b1eaa9a 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -37,3 +37,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_. favicon streaming deferredcallbacks + methodoverrides diff --git a/docs/patterns/methodoverrides.rst b/docs/patterns/methodoverrides.rst new file mode 100644 index 00000000..d5c187b6 --- /dev/null +++ b/docs/patterns/methodoverrides.rst @@ -0,0 +1,43 @@ +Adding HTTP Method Overrides +============================ + +Some HTTP proxies do not support arbitrary HTTP methods or newer HTTP +methods (such as PATCH). In that case it's possible to “proxy” HTTP +methods through another HTTP method in total violation of the protocol. + +The way this works is by letting the client do an HTTP POST request and +set the ``X-HTTP-Method-Override`` header and set the value to the +intended HTTP method (such as ``PATCH``). + +This can easily be accomplished with an HTTP middleware:: + + class HTTPMethodOverrideMiddleware(object): + allowed_methods = frozenset([ + 'GET', + 'HEAD', + 'POST', + 'DELETE', + 'PUT', + 'PATCH', + 'OPTIONS' + ]) + bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE']) + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + method = environ.get('HTTP_X_HTTP_METHOD_OVERRIDE', '').upper() + if method in self.allowed_methods: + method = method.encode('ascii', 'replace') + environ['REQUEST_METHOD'] = method + if method in self.bodyless_methods: + environ['CONTENT_LENGTH'] = '0' + return self.app(environ, start_response) + +To use this with Flask this is all that is necessary:: + + from flask import Flask + + app = Flask(__name__) + app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app) From 5b462dd38224648e7a3e533449784a9e236f957d Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 14:50:21 +0200 Subject: [PATCH 0236/2940] Fixed a typo in the docs. This fixes #576 and #575 --- docs/signals.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/signals.rst b/docs/signals.rst index 959c53bd..5f0af048 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -285,7 +285,7 @@ The following signals exist in Flask: def close_db_connection(sender, **extra): session.close() - from flask import request_tearing_down + from flask import appcontext_tearing_down appcontext_tearing_down.connect(close_db_connection, app) This will also be passed an `exc` keyword argument that has a reference From 7f8709147435bea69fa15ae0de9a2326ab5dfc41 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 14:51:26 +0200 Subject: [PATCH 0237/2940] Added a missing exposed import. This fixes #575 --- flask/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index ac1a3a00..fda94f39 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -34,7 +34,8 @@ from .templating import render_template, render_template_string # the signals from .signals import signals_available, template_rendered, request_started, \ - request_finished, got_request_exception, request_tearing_down + request_finished, got_request_exception, request_tearing_down, \ + appcontext_tearing_down # only import json if it's available from .helpers import json From de5038f2fb25189e6d2ba8bc479d3d940a8ea43b Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 14:56:02 +0200 Subject: [PATCH 0238/2940] Added total_seconds() helper for pythons before 2.7 --- flask/sessions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flask/sessions.py b/flask/sessions.py index 4790f73a..343fa94d 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -19,6 +19,10 @@ from . import Markup from itsdangerous import URLSafeTimedSerializer, BadSignature +def total_seconds(td): + return td.days * 60 * 60 * 24 + td.seconds + + class SessionMixin(object): """Expands a basic dictionary with an accessors that are expected by Flask extensions and users for the session. @@ -267,7 +271,7 @@ class SecureCookieSessionInterface(SessionInterface): val = request.cookies.get(app.session_cookie_name) if not val: return self.session_class() - max_age = app.permanent_session_lifetime.total_seconds() + max_age = total_seconds(app.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) return self.session_class(data) From 3bec75d230507335eaf9066605d3893e185b9e11 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 15:24:03 +0200 Subject: [PATCH 0239/2940] Set the content-length header for sendfile. Fixes #447 --- CHANGES | 1 + flask/helpers.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index be7ede79..0c7a8e1e 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Release date to be decided. :ref:`upgrading-to-010` for more information. - Added ``template_test`` methods in addition to the already existing ``template_filter`` method family. +- Set the content-length header for x-sendfile. Version 0.9 ----------- diff --git a/flask/helpers.py b/flask/helpers.py index b2d34a71..07c8add2 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -541,6 +541,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if file is not None: file.close() headers['X-Sendfile'] = filename + headers['Content-Length'] = os.path.getsize(filename) data = None else: if file is None: From c4f2075f4c4c27856fe0af77250fb75c61c0d86b Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 15:33:25 +0200 Subject: [PATCH 0240/2940] tojson no longer escapes script blocks in HTML5 parsers. Fixed #605 --- CHANGES | 1 + flask/helpers.py | 12 +++++++----- flask/testsuite/helpers.py | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 0c7a8e1e..10a2fe1c 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Release date to be decided. - Added ``template_test`` methods in addition to the already existing ``template_filter`` method family. - Set the content-length header for x-sendfile. +- ``tojson`` filter now does not escape script blocks in HTML5 parsers. Version 0.9 ----------- diff --git a/flask/helpers.py b/flask/helpers.py index 07c8add2..9491ac55 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -45,11 +45,13 @@ from .globals import session, _request_ctx_stack, _app_ctx_stack, \ # figure out if simplejson escapes slashes. This behavior was changed # from one version to another without reason. -if '\\/' not in json.dumps('/'): - def _tojson_filter(*args, **kwargs): - return json.dumps(*args, **kwargs).replace('/', '\\/') -else: - _tojson_filter = json.dumps +_slash_escape = '\\/' not in json.dumps('/') + +def _tojson_filter(*args, **kwargs): + rv = json.dumps(*args, **kwargs) + if _slash_escape: + rv = rv.replace('/', '\\/') + return rv.replace('<!', '<\\u0021') # sentinel diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 54c01482..58592668 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -97,6 +97,8 @@ class JSONTestCase(FlaskTestCase): self.assert_equal(rv, '"<\\/script>"') rv = render('{{ "<\0/script>"|tojson|safe }}') self.assert_equal(rv, '"<\\u0000\\/script>"') + rv = render('{{ "<!--<script>"|tojson|safe }}') + self.assert_equal(rv, '"<\\u0021--<script>"') def test_modified_url_encoding(self): class ModifiedRequest(flask.Request): From b5bb49d080475e4fb90f6a47dacab60b53470e9d Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 15:46:21 +0200 Subject: [PATCH 0241/2940] Added a new example for checksums on input data. This fixes #601 --- docs/patterns/index.rst | 1 + docs/patterns/requestchecksum.rst | 55 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 docs/patterns/requestchecksum.rst diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 2b1eaa9a..4a09340f 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -38,3 +38,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_. streaming deferredcallbacks methodoverrides + requestchecksum diff --git a/docs/patterns/requestchecksum.rst b/docs/patterns/requestchecksum.rst new file mode 100644 index 00000000..902be64a --- /dev/null +++ b/docs/patterns/requestchecksum.rst @@ -0,0 +1,55 @@ +Request Content Checksums +========================= + +Various pieces of code can consume the request data and preprocess it. +For instance JSON data ends up on the request object already read and +processed, form data ends up there as well but goes through a different +code path. This seems inconvenient when you want to calculate the +checksum of the incoming request data. This is necessary sometimes for +some APIs. + +Fortunately this is however very simple to change by wrapping the input +stream. + +The following example calculates the SHA1 checksum of the incoming data as +it gets read and stores it in the WSGI environment:: + + import hashlib + + class ChecksumCalcStream(object): + + def __init__(self, stream): + self._stream = stream + self._hash = hashlib.sha1() + + def read(self, bytes): + rv = self._stream.read(bytes) + self._hash.update(rv) + return rv + + def readline(self, size_hint): + rv = self._stream.readline(size_hint) + self._hash.update(rv) + return rv + + def generate_checksum(request): + env = request.environ + stream = ChecksumCalcStream(env['wsgi.input']) + env['wsgi.input'] = stream + return stream._hash + +To use this, all you need to do is to hook the calculating stream in +before the request starts consuming data. (Eg: be careful accessing +``request.form`` or anything of that nature. ``before_request_handlers`` +for instance should be careful not to access it). + +Example usage:: + + @app.route('/special-api', methods=['POST']) + def special_api(): + hash = generate_checksum(request) + # Accessing this parses the input stream + files = request.files + # At this point the hash is fully constructed. + checksum = hash.hexdigest() + return 'Hash was: %s' % checksum From 3c1d7758d54a5ad2c9fa1ea0933ed10b6cfeb696 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 15:58:21 +0200 Subject: [PATCH 0242/2940] Removed dev tag from setup.cfg. Fixes #596 --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2d74c58f..6116ecd1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [egg_info] -tag_build = dev tag_date = true [aliases] From f8b6033a3b86691470a36db57eba17d996c7d5e8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 16:04:31 +0200 Subject: [PATCH 0243/2940] Added a workaround for samefile. This fixes #600 --- flask/testsuite/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 76a4d724..5d86fa3d 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -32,9 +32,12 @@ def add_to_path(path): raise RuntimeError('Tried to add nonexisting path') def _samefile(x, y): + if x == y: + return True try: return os.path.samefile(x, y) - except (IOError, OSError): + except (IOError, OSError, AttributeError): + # Windows has no samefile return False sys.path[:] = [x for x in sys.path if not _samefile(path, x)] sys.path.insert(0, path) From b6f37c40f8d426f4af340b722d28ccea7f4caf2a Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 16:08:00 +0200 Subject: [PATCH 0244/2940] Updated travis config for notifications --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8cd434d1..6d0ab2d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,3 +13,13 @@ script: python setup.py test branches: except: - website + +notifications: + # Disable travis notifications until they figured out how to hide + # their own builder failure from us. Travis currently fails way + # too many times by itself. + email: false + + irc: + channels: + - "irc.freenode.org#pocoo" From 6e4015d62427a220206d58742ae3e54439ecd97a Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 16:08:13 +0200 Subject: [PATCH 0245/2940] Removed tox file --- Makefile | 3 --- tox.ini | 8 -------- 2 files changed, 11 deletions(-) delete mode 100644 tox.ini diff --git a/Makefile b/Makefile index 08811ac2..773c680e 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,6 @@ audit: release: python scripts/make-release.py -tox-test: - PYTHONDONTWRITEBYTECODE= tox - ext-test: python tests/flaskext_test.py --browse diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 82c1588e..00000000 --- a/tox.ini +++ /dev/null @@ -1,8 +0,0 @@ -[tox] -envlist=py25,py26,py27,pypy - -[testenv] -commands=python run-tests.py - -[testenv:py25] -deps=simplejson From e2b3f07d7c94793414693a310d54c667e0a8da7d Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 16:16:21 +0200 Subject: [PATCH 0246/2940] Stop the joinspam --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6d0ab2d6..fd590bea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,3 +23,5 @@ notifications: irc: channels: - "irc.freenode.org#pocoo" + use_notice: true + skip_join: true From 661ee54bc2bc1ea0763ac9c226f8e14bb0beb5b1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 17:12:16 +0200 Subject: [PATCH 0247/2940] Raise exceptions if a function is overridden by a new endpoint. This fixes #570 --- flask/app.py | 5 +++++ flask/testsuite/basic.py | 2 +- flask/testsuite/views.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 91db1438..327cb8cf 100644 --- a/flask/app.py +++ b/flask/app.py @@ -942,8 +942,13 @@ class Flask(_PackageBoundObject): rule = self.url_rule_class(rule, methods=methods, **options) rule.provide_automatic_options = provide_automatic_options + self.url_map.add(rule) if view_func is not None: + old_func = self.view_functions.get(endpoint) + if old_func is not None and old_func is not view_func: + raise AssertionError('View function mapping is overwriting an ' + 'existing endpoint function: %s' % endpoint) self.view_functions[endpoint] = view_func def route(self, rule, **options): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 3d758b3a..964d2c18 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -387,7 +387,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return '' @app.route('/test_filters_without_returning_categories/') - def test_filters(): + def test_filters2(): messages = flask.get_flashed_messages(category_filter=['message', 'warning']) self.assert_equal(len(messages), 2) self.assert_equal(messages[0], u'Hello World') diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py index c7cb0a8a..350eb7f2 100644 --- a/flask/testsuite/views.py +++ b/flask/testsuite/views.py @@ -145,6 +145,23 @@ class ViewTestCase(FlaskTestCase): self.assert_equal(rv.data, '') self.assert_equal(rv.headers['X-Method'], 'HEAD') + def test_endpoint_override(self): + app = flask.Flask(__name__) + app.debug = True + + class Index(flask.views.View): + methods = ['GET', 'POST'] + def dispatch_request(self): + return flask.request.method + + app.add_url_rule('/', view_func=Index.as_view('index')) + + with self.assert_raises(AssertionError): + app.add_url_rule('/', view_func=Index.as_view('index')) + + # But these tests should still pass. We just log a warning. + self.common_test(app) + def suite(): suite = unittest.TestSuite() From f701f699477800497636fd5b28132744ec68928d Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 17:13:12 +0200 Subject: [PATCH 0248/2940] Documented new error case --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 10a2fe1c..b79c6814 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,8 @@ Release date to be decided. ``template_filter`` method family. - Set the content-length header for x-sendfile. - ``tojson`` filter now does not escape script blocks in HTML5 parsers. +- Flask will now raise an error if you attempt to register a new function + on an already used endpoint. Version 0.9 ----------- From 3afcbf160eff2a5ab6ac35a82e0719f972df8972 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 22:58:41 +0200 Subject: [PATCH 0249/2940] Extra safety for safe_join. Does not look exploitable but better safe than sorry. Fixes #501 --- flask/helpers.py | 4 +++- flask/testsuite/regression.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 9491ac55..9bcb22bb 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -604,7 +604,9 @@ def safe_join(directory, filename): for sep in _os_alt_seps: if sep in filename: raise NotFound() - if os.path.isabs(filename) or filename.startswith('../'): + if os.path.isabs(filename) or \ + filename == '..' or \ + filename.startswith('../'): raise NotFound() return os.path.join(directory, filename) diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py index bc37afc4..87a6289b 100644 --- a/flask/testsuite/regression.py +++ b/flask/testsuite/regression.py @@ -17,6 +17,7 @@ import flask import threading import unittest from werkzeug.test import run_wsgi_app, create_environ +from werkzeug.exceptions import NotFound from flask.testsuite import FlaskTestCase @@ -79,6 +80,11 @@ class MemoryTestCase(FlaskTestCase): for x in xrange(10): fire() + def test_safe_join_toplevel_pardir(self): + from flask.helpers import safe_join + with self.assert_raises(NotFound): + safe_join('/foo', '..') + def suite(): suite = unittest.TestSuite() From 301e244df3dc747a73e2564a046976af5d0164eb Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 22:59:52 +0200 Subject: [PATCH 0250/2940] Consistent use of encoding naming --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 9bcb22bb..8780ee20 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -571,7 +571,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, os.path.getmtime(filename), os.path.getsize(filename), adler32( - filename.encode('utf8') if isinstance(filename, unicode) + filename.encode('utf-8') if isinstance(filename, unicode) else filename ) & 0xffffffff )) From b146d8277ab90cf6d43ea54113383076e4fd0318 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 23:31:48 +0200 Subject: [PATCH 0251/2940] Added wrapper module around simplejson/json for much simplified customization. --- CHANGES | 3 + docs/api.rst | 78 +++++++++++++++-------- flask/__init__.py | 14 +++-- flask/app.py | 16 ++++- flask/exceptions.py | 3 +- flask/helpers.py | 46 -------------- flask/json.py | 146 ++++++++++++++++++++++++++++++++++++++++++++ flask/sessions.py | 3 +- flask/wrappers.py | 2 +- 9 files changed, 227 insertions(+), 84 deletions(-) create mode 100644 flask/json.py diff --git a/CHANGES b/CHANGES index b79c6814..2a87cbe2 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,9 @@ Release date to be decided. - ``tojson`` filter now does not escape script blocks in HTML5 parsers. - Flask will now raise an error if you attempt to register a new function on an already used endpoint. +- Added wrapper module around simplejson and added default serialization + of datetime objects. This allows much easier customization of how + JSON is handled by Flask or any Flask extension. Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index 316c76ab..dbd1877e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -312,43 +312,71 @@ Message Flashing .. autofunction:: get_flashed_messages -Returning JSON --------------- +JSON Support +------------ + +.. module:: flask.json + +Flask uses ``simplejson`` for the JSON implementation. Since simplejson +is provided both by the standard library as well as extension Flask will +try simplejson first and then fall back to the stdlib json module. On top +of that it will delegate access to the current application's JSOn encoders +and decoders for easier customization. + +So for starters instead of doing:: + + try: + import simplejson as json + except ImportError: + import json + +You can instead just do this:: + + from flask import json + +For usage examples, read the :mod:`json` documentation in the standard +lirbary. The following extensions are by default applied to the stdlib's +JSON module: + +1. ``datetime`` objects are serialized as :rfc:`822` strings. +2. Any object with an ``__html__`` method (like :class:`~flask.Markup`) + will ahve that method called and then the return value is serialized + as string. + +The :func:`~htmlsafe_dumps` function of this json module is also available +as filter called ``|tojson`` in Jinja2. Note that inside `script` +tags no escaping must take place, so make sure to disable escaping +with ``|safe`` if you intend to use it inside `script` tags: + +.. sourcecode:: html+jinja + + <script type=text/javascript> + doSomethingWith({{ user.username|tojson|safe }}); + </script> + +Note that the ``|tojson`` filter escapes forward slashes properly. .. autofunction:: jsonify -.. data:: json +.. autofunction:: dumps - If JSON support is picked up, this will be the module that Flask is - using to parse and serialize JSON. So instead of doing this yourself:: +.. autofunction:: dump - try: - import simplejson as json - except ImportError: - import json +.. autofunction:: loads - You can instead just do this:: +.. autofunction:: load - from flask import json +.. autoclass:: JSONEncoder + :members: - For usage examples, read the :mod:`json` documentation. - - The :func:`~json.dumps` function of this json module is also available - as filter called ``|tojson`` in Jinja2. Note that inside `script` - tags no escaping must take place, so make sure to disable escaping - with ``|safe`` if you intend to use it inside `script` tags: - - .. sourcecode:: html+jinja - - <script type=text/javascript> - doSomethingWith({{ user.username|tojson|safe }}); - </script> - - Note that the ``|tojson`` filter escapes forward slashes properly. +.. autoclass:: JSONDecoder + :members: Template Rendering ------------------ +.. currentmodule:: flask + .. autofunction:: render_template .. autofunction:: render_template_string diff --git a/flask/__init__.py b/flask/__init__.py index fda94f39..6e7883fb 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -20,9 +20,8 @@ from jinja2 import Markup, escape from .app import Flask, Request, Response from .config import Config -from .helpers import url_for, jsonify, flash, \ - send_file, send_from_directory, get_flashed_messages, \ - get_template_attribute, make_response, safe_join, \ +from .helpers import url_for, flash, send_file, send_from_directory, \ + get_flashed_messages, get_template_attribute, make_response, safe_join, \ stream_with_context from .globals import current_app, g, request, session, _request_ctx_stack, \ _app_ctx_stack @@ -37,8 +36,13 @@ from .signals import signals_available, template_rendered, request_started, \ request_finished, got_request_exception, request_tearing_down, \ appcontext_tearing_down -# only import json if it's available -from .helpers import json +# We're not exposing the actual json module but a convenient wrapper around +# it. +from . import json + +# This was the only thing that flask used to export at one point and it had +# a more generic name. +jsonify = json.jsonify # backwards compat, goes away in 1.0 from .sessions import SecureCookieSession as Session diff --git a/flask/app.py b/flask/app.py index 327cb8cf..1763aa74 100644 --- a/flask/app.py +++ b/flask/app.py @@ -24,8 +24,8 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed, BadRequest from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ - locked_cached_property, _tojson_filter, _endpoint_from_view_func, \ - find_package + locked_cached_property, _endpoint_from_view_func, find_package +from . import json from .wrappers import Request, Response from .config import ConfigAttribute, Config from .ctx import RequestContext, AppContext, _RequestGlobals @@ -238,6 +238,16 @@ class Flask(_PackageBoundObject): '-' * 80 ) + #: The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`. + #: + #: .. versionadded:: 0.10 + json_encoder = json.JSONEncoder + + #: The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`. + #: + #: .. versionadded:: 0.10 + json_decoder = json.JSONDecoder + #: Options that are passed directly to the Jinja2 environment. jinja_options = ImmutableDict( extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] @@ -637,7 +647,7 @@ class Flask(_PackageBoundObject): url_for=url_for, get_flashed_messages=get_flashed_messages ) - rv.filters['tojson'] = _tojson_filter + rv.filters['tojson'] = json.htmlsafe_dumps return rv def create_global_jinja_loader(self): diff --git a/flask/exceptions.py b/flask/exceptions.py index 9ccdedab..83b9556b 100644 --- a/flask/exceptions.py +++ b/flask/exceptions.py @@ -9,7 +9,7 @@ :license: BSD, see LICENSE for more details. """ from werkzeug.exceptions import HTTPException, BadRequest -from .helpers import json +from . import json class JSONHTTPException(HTTPException): @@ -39,7 +39,6 @@ class JSONHTTPException(HTTPException): class JSONBadRequest(JSONHTTPException, BadRequest): """Represents an HTTP ``400 Bad Request`` error whose body contains an error message in JSON format instead of HTML format (as in the superclass). - """ #: The description of the error which occurred as a string. diff --git a/flask/helpers.py b/flask/helpers.py index 8780ee20..3f06c116 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -23,10 +23,6 @@ from werkzeug.routing import BuildError from werkzeug.urls import url_quote from functools import update_wrapper -# Use the same json implementation as itsdangerous on which we -# depend anyways. -from itsdangerous import simplejson as json - from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound @@ -43,17 +39,6 @@ from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request -# figure out if simplejson escapes slashes. This behavior was changed -# from one version to another without reason. -_slash_escape = '\\/' not in json.dumps('/') - -def _tojson_filter(*args, **kwargs): - rv = json.dumps(*args, **kwargs) - if _slash_escape: - rv = rv.replace('/', '\\/') - return rv.replace('<!', '<\\u0021') - - # sentinel _missing = object() @@ -146,37 +131,6 @@ def stream_with_context(generator_or_function): return wrapped_g -def jsonify(*args, **kwargs): - """Creates a :class:`~flask.Response` with the JSON representation of - the given arguments with an `application/json` mimetype. The arguments - to this function are the same as to the :class:`dict` constructor. - - Example usage:: - - @app.route('/_get_current_user') - def get_current_user(): - return jsonify(username=g.user.username, - email=g.user.email, - id=g.user.id) - - This will send a JSON response like this to the browser:: - - { - "username": "admin", - "email": "admin@localhost", - "id": 42 - } - - This requires Python 2.6 or an installed version of simplejson. For - security reasons only objects are supported toplevel. For more - information about this, have a look at :ref:`json-security`. - - .. versionadded:: 0.2 - """ - return current_app.response_class(json.dumps(dict(*args, **kwargs), - indent=None if request.is_xhr else 2), mimetype='application/json') - - def make_response(*args): """Sometimes it is necessary to set additional headers in a view. Because views do not have to return response objects but can return a value that diff --git a/flask/json.py b/flask/json.py new file mode 100644 index 00000000..149cfbab --- /dev/null +++ b/flask/json.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +""" + flask.jsonimpl + ~~~~~~~~~~~~~~ + + Implementation helpers for the JSON support in Flask. + + :copyright: (c) 2012 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from datetime import datetime +from .globals import current_app, request + +from werkzeug.http import http_date + +# Use the same json implementation as itsdangerous on which we +# depend anyways. +from itsdangerous import simplejson as _json + + +# figure out if simplejson escapes slashes. This behavior was changed +# from one version to another without reason. +_slash_escape = '\\/' not in _json.dumps('/') + + +__all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump', + 'htmlsafe_dumps', 'JSONDecoder', 'JSONEncoder', + 'jsonify'] + + +class JSONEncoder(_json.JSONEncoder): + """The default Flask JSON encoder. This one extends the default simplejson + encoder by also supporting ``datetime`` objects as well as ``Markup`` + objects which are serialized as RFC 822 datetime strings (same as the HTTP + date format). In order to support more data types override the + :meth:`default` method. + """ + + def default(self, o): + """Implement this method in a subclass such that it returns a + serializable object for ``o``, or calls the base implementation (to + raise a ``TypeError``). + + For example, to support arbitrary iterators, you could implement + default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + """ + if isinstance(o, datetime): + return http_date(o) + if hasattr(o, '__html__'): + return unicode(o.__html__()) + return _json.JSONEncoder.default(self, o) + + +class JSONDecoder(_json.JSONDecoder): + """The default JSON decoder. This one does not change the behavior from + the default simplejson encoder. Consult the :mod:`json` documentation + for more information. This decoder is not only used for the load + functions of this module but also :attr:`~flask.Request`. + """ + + +def dumps(obj, **kwargs): + """Serialize ``obj`` to a JSON formatted ``str`` by using the application's + configured encoder (:attr:`~flask.Flask.json_encoder`). + """ + kwargs.setdefault('cls', current_app.json_encoder) + return _json.dumps(obj, **kwargs) + + +def dump(obj, fp, **kwargs): + """Like :func:`dumps` but writes into a file object.""" + kwargs.setdefault('cls', current_app.json_encoder) + return _json.dump(obj, fp, **kwargs) + + +def loads(s, **kwargs): + """Unserialize a JSON object from a string ``s`` by using the application's + configured decoder (:attr:`~flask.Flask.json_decoder`). + """ + kwargs.setdefault('cls', current_app.json_decoder) + return _json.loads(s, **kwargs) + + +def load(fp, **kwargs): + """Like :func:`loads` but reads from a file object. + """ + kwargs.setdefault('cls', current_app.json_decoder) + return _json.load(fp, **kwargs) + + +def htmlsafe_dumps(obj, **kwargs): + """Works exactly like :func:`dumps` but is safe for use in ``<script>`` + tags. It accepts the same arguments and returns a JSON string. Note that + this is available in templates through the ``|tojson`` filter but it will + have to be wrapped in ``|safe`` unless **true** XHTML is being used. + """ + rv = dumps(obj, **kwargs) + if _slash_escape: + rv = rv.replace('/', '\\/') + return rv.replace('<!', '<\\u0021') + + +def htmlsafe_dump(obj, fp, **kwargs): + """Like :func:`htmlsafe_dumps` but writes into a file object.""" + fp.write(htmlsafe_dumps(obj, **kwargs)) + + +def jsonify(*args, **kwargs): + """Creates a :class:`~flask.Response` with the JSON representation of + the given arguments with an `application/json` mimetype. The arguments + to this function are the same as to the :class:`dict` constructor. + + Example usage:: + + @app.route('/_get_current_user') + def get_current_user(): + return jsonify(username=g.user.username, + email=g.user.email, + id=g.user.id) + + This will send a JSON response like this to the browser:: + + { + "username": "admin", + "email": "admin@localhost", + "id": 42 + } + + This requires Python 2.6 or an installed version of simplejson. For + security reasons only objects are supported toplevel. For more + information about this, have a look at :ref:`json-security`. + + .. versionadded:: 0.2 + """ + return current_app.response_class(dumps(dict(*args, **kwargs), + indent=None if request.is_xhr else 2), + mimetype='application/json') diff --git a/flask/sessions.py b/flask/sessions.py index 343fa94d..46ce0830 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -13,8 +13,7 @@ import hashlib from datetime import datetime from werkzeug.http import http_date, parse_date from werkzeug.datastructures import CallbackDict -from .helpers import json -from . import Markup +from . import Markup, json from itsdangerous import URLSafeTimedSerializer, BadSignature diff --git a/flask/wrappers.py b/flask/wrappers.py index cd1a6ec9..a56fe5d7 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -14,7 +14,7 @@ from werkzeug.utils import cached_property from .exceptions import JSONBadRequest from .debughelpers import attach_enctype_error_multidict -from .helpers import json +from . import json from .globals import _request_ctx_stack From 05c6502cbde016f0475233faa0c6364634cccac9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 7 Oct 2012 23:41:41 +0200 Subject: [PATCH 0252/2940] Let json.* work even without app on the stack and added tests --- flask/json.py | 18 ++++++++++++------ flask/testsuite/helpers.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/flask/json.py b/flask/json.py index 149cfbab..1593a326 100644 --- a/flask/json.py +++ b/flask/json.py @@ -70,30 +70,36 @@ class JSONDecoder(_json.JSONDecoder): def dumps(obj, **kwargs): """Serialize ``obj`` to a JSON formatted ``str`` by using the application's - configured encoder (:attr:`~flask.Flask.json_encoder`). + configured encoder (:attr:`~flask.Flask.json_encoder`) if there is an + application on the stack. """ - kwargs.setdefault('cls', current_app.json_encoder) + if current_app: + kwargs.setdefault('cls', current_app.json_encoder) return _json.dumps(obj, **kwargs) def dump(obj, fp, **kwargs): """Like :func:`dumps` but writes into a file object.""" - kwargs.setdefault('cls', current_app.json_encoder) + if current_app: + kwargs.setdefault('cls', current_app.json_encoder) return _json.dump(obj, fp, **kwargs) def loads(s, **kwargs): """Unserialize a JSON object from a string ``s`` by using the application's - configured decoder (:attr:`~flask.Flask.json_decoder`). + configured decoder (:attr:`~flask.Flask.json_decoder`) if there is an + application on the stack. """ - kwargs.setdefault('cls', current_app.json_decoder) + if current_app: + kwargs.setdefault('cls', current_app.json_decoder) return _json.loads(s, **kwargs) def load(fp, **kwargs): """Like :func:`loads` but reads from a file object. """ - kwargs.setdefault('cls', current_app.json_decoder) + if current_app: + kwargs.setdefault('cls', current_app.json_decoder) return _json.load(fp, **kwargs) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 58592668..ee5312e0 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -100,6 +100,36 @@ class JSONTestCase(FlaskTestCase): rv = render('{{ "<!--<script>"|tojson|safe }}') self.assert_equal(rv, '"<\\u0021--<script>"') + def test_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 + app = flask.Flask(__name__) + app.testing = True + app.json_encoder = MyEncoder + app.json_decoder = MyDecoder + @app.route('/', methods=['POST']) + def index(): + return flask.json.dumps(flask.request.json['x']) + c = app.test_client() + rv = c.post('/', data=flask.json.dumps({ + 'x': {'_foo': 42} + }), content_type='application/json') + self.assertEqual(rv.data, '"<42>"') + def test_modified_url_encoding(self): class ModifiedRequest(flask.Request): url_charset = 'euc-kr' From 5e88c8184d19851c4ccc9a74595819bbe374e839 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 8 Oct 2012 06:48:13 +0200 Subject: [PATCH 0253/2940] Removed deprecated and awkward flask.session module --- CHANGES | 3 +++ flask/session.py | 19 ------------------- 2 files changed, 3 insertions(+), 19 deletions(-) delete mode 100644 flask/session.py diff --git a/CHANGES b/CHANGES index 2a87cbe2..00978cdd 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,9 @@ Release date to be decided. - Added wrapper module around simplejson and added default serialization of datetime objects. This allows much easier customization of how JSON is handled by Flask or any Flask extension. +- Removed deprecated internal ``flask.session`` module alias. Use + ``flask.sessions`` instead to get the session module. This is not to + be confused with ``flask.session`` the session proxy. Version 0.9 ----------- diff --git a/flask/session.py b/flask/session.py deleted file mode 100644 index 1a43fdc1..00000000 --- a/flask/session.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -""" - flask.session - ~~~~~~~~~~~~~ - - This module used to flask with the session global so we moved it - over to flask.sessions - - :copyright: (c) 2011 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" - -from warnings import warn -warn(DeprecationWarning('please use flask.sessions instead')) - -from .sessions import SecureCookieSession, NullSession - -Session = SecureCookieSession -_NullSession = NullSession From f34c0281252bf1838e2ec24fe8b064b232a098ef Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 8 Oct 2012 07:01:49 +0200 Subject: [PATCH 0254/2940] Added template tests and made config a true global --- CHANGES | 7 +++++++ docs/templating.rst | 15 ++++++++++++--- flask/app.py | 11 +++++++---- flask/templating.py | 9 +++++---- flask/testsuite/templating.py | 12 ++++++++++++ 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 00978cdd..fd9d110c 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,13 @@ Release date to be decided. - Removed deprecated internal ``flask.session`` module alias. Use ``flask.sessions`` instead to get the session module. This is not to be confused with ``flask.session`` the session proxy. +- Templates can now be rendered without request context. The behavior is + slightly different as the ``request``, ``session`` and ``g`` objects + will not be available and blueprint's context processors are not + called. +- The config object is now available to the template as a real global and + not through a context processor which makes it available even in imported + templates by default. Version 0.9 ----------- diff --git a/docs/templating.rst b/docs/templating.rst index 8ecf5332..166f26aa 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -38,20 +38,29 @@ by default: .. versionadded:: 0.6 + .. versionchanged:: 0.10 + This is now always available, even in imported templates. + .. data:: request :noindex: - The current request object (:class:`flask.request`) + The current request object (:class:`flask.request`). This variable is + unavailable if the template was rendered without an active request + context. .. data:: session :noindex: - The current session object (:class:`flask.session`) + The current session object (:class:`flask.session`). This variable + is unavailable if the template was rendered without an active request + context. .. data:: g :noindex: - The request-bound object for global variables (:data:`flask.g`) + The request-bound object for global variables (:data:`flask.g`). This + variable is unavailable if the template was rendered without an active + request context. .. function:: url_for :noindex: diff --git a/flask/app.py b/flask/app.py index 1763aa74..87ba933e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -645,7 +645,8 @@ class Flask(_PackageBoundObject): rv = Environment(self, **options) rv.globals.update( url_for=url_for, - get_flashed_messages=get_flashed_messages + get_flashed_messages=get_flashed_messages, + config=self.config ) rv.filters['tojson'] = json.htmlsafe_dumps return rv @@ -694,9 +695,11 @@ class Flask(_PackageBoundObject): to add extra variables. """ funcs = self.template_context_processors[None] - bp = _request_ctx_stack.top.request.blueprint - if bp is not None and bp in self.template_context_processors: - funcs = chain(funcs, self.template_context_processors[bp]) + reqctx = _request_ctx_stack.top + if reqctx is not None: + bp = reqctx.request.blueprint + if bp is not None and bp in self.template_context_processors: + funcs = chain(funcs, self.template_context_processors[bp]) orig_ctx = context.copy() for func in funcs: context.update(func()) diff --git a/flask/templating.py b/flask/templating.py index e5d896cb..2bac22e9 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -12,7 +12,7 @@ import posixpath from jinja2 import BaseLoader, Environment as BaseEnvironment, \ TemplateNotFound -from .globals import _request_ctx_stack +from .globals import _request_ctx_stack, _app_ctx_stack from .signals import template_rendered from .module import blueprint_is_module @@ -22,8 +22,9 @@ def _default_template_ctx_processor(): `session` and `g`. """ reqctx = _request_ctx_stack.top + if reqctx is None: + return {} return dict( - config=reqctx.app.config, request=reqctx.request, session=reqctx.session, g=reqctx.g @@ -119,7 +120,7 @@ def render_template(template_name_or_list, **context): :param context: the variables that should be available in the context of the template. """ - ctx = _request_ctx_stack.top + ctx = _app_ctx_stack.top ctx.app.update_template_context(context) return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list), context, ctx.app) @@ -134,7 +135,7 @@ def render_template_string(source, **context): :param context: the variables that should be available in the context of the template. """ - ctx = _request_ctx_stack.top + ctx = _app_ctx_stack.top ctx.app.update_template_context(context) return _render(ctx.app.jinja_env.from_string(source), context, ctx.app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 1df4292d..6345b710 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -37,6 +37,18 @@ class TemplatingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.data, '42') + def test_request_less_rendering(self): + app = flask.Flask(__name__) + app.config['WORLD_NAME'] = 'Special World' + @app.context_processor + def context_processor(): + return dict(foo=42) + + with app.app_context(): + rv = flask.render_template_string('Hello {{ config.WORLD_NAME }} ' + '{{ foo }}') + self.assert_equal(rv, 'Hello Special World 42') + def test_standard_context(self): app = flask.Flask(__name__) app.secret_key = 'development key' From 3e9f4e254b5757d80218c0d8077bd44465365489 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 8 Oct 2012 07:05:32 +0200 Subject: [PATCH 0255/2940] Updated a comment that was misleading with recent flask sqlalchemy installations --- flask/ctx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flask/ctx.py b/flask/ctx.py index 3ea42a27..84e96575 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -213,7 +213,7 @@ class RequestContext(object): # on the stack. The rationale is that you want to access that # information under debug situations. However if someone forgets to # pop that context again we want to make sure that on the next push - # it's invalidated otherwise we run at risk that something leaks + # it's invalidated, otherwise we run at risk that something leaks # memory. This is usually only a problem in testsuite since this # functionality is not active in production environments. top = _request_ctx_stack.top @@ -234,7 +234,8 @@ class RequestContext(object): # Open the session at the moment that the request context is # available. This allows a custom open_session method to use the - # request context (e.g. flask-sqlalchemy). + # request context (e.g. code that access database information + # stored on `g` instead of the appcontext). self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session() From c2e5799879ed22a4657b75df6da66d6074ae7cc7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 9 Oct 2012 14:02:32 -0500 Subject: [PATCH 0256/2940] Updated examples to new sqlite patterns and added new section to appcontext docs --- docs/appcontext.rst | 44 ++++++++++++++ docs/patterns/sqlite3.rst | 107 +++++++++++++++++++--------------- examples/flaskr/flaskr.py | 41 +++++++------ examples/minitwit/minitwit.py | 81 +++++++++++++------------ 4 files changed, 169 insertions(+), 104 deletions(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index 928ffc8a..a6b67001 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -85,3 +85,47 @@ Extensions are free to store additional information on the topmost level, assuming they pick a sufficiently unique name. For more information about that, see :ref:`extension-dev`. + +Context Usage +------------- + +The context is typically used to cache resources on there that need to be +created on a per-request or usage case. For instance database connects +are destined to go there. When storing things on the application context +unique names should be chosen as this is a place that is shared between +Flask applications and extensions. + +The most common usage is to split resource management into two parts: + +1. an implicit resource caching on the context. +2. a context teardown based resource deallocation. + +Generally there would be a ``get_X()`` function that creates resource +``X`` if it does not exist yet and otherwise returns the same resource, +and a ``teardown_X()`` function that is registered as teardown handler. + +This is an example that connects to a database:: + + import sqlite3 + from flask import _app_ctx_stack + + def get_db(): + top = _app_ctx_stack.top + if not hasattr(top, 'database'): + top.database = connect_to_database() + return top.database + + @app.teardown_appcontext + def teardown_db(exception): + top = _app_ctx_stack.top + if hasattr(top, 'database'): + top.database.close() + +The first time ``get_db()`` is called the connection will be established. +To make this implicit a :class:`~werkzeug.local.LocalProxy` can be used:: + + from werkzeug.local import LocalProxy + db = LocalProxy(get_db) + +That way a user can directly access ``db`` which internally calls +``get_db()``. diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 0d02e465..45bcc959 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -3,61 +3,61 @@ Using SQLite 3 with Flask ========================= -In Flask you can implement the opening of database connections at the -beginning of the request and closing at the end with the -:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.teardown_request` -decorators in combination with the special :class:`~flask.g` object. +In Flask you can implement the opening of database connections on demand +and closing it when the context dies (usually at the end of the request) +easily. -So here is a simple example of how you can use SQLite 3 with Flask:: +Here is a simple example of how you can use SQLite 3 with Flask:: import sqlite3 - from flask import g + from flask import _app_ctx_stack DATABASE = '/path/to/database.db' - def connect_db(): - return sqlite3.connect(DATABASE) + def get_db(): + top = _app_ctx_stack.top + if not hasattr(top, 'sqlite_db'): + top.sqlite_db = sqlite3.connect(DATABASE) + return top.sqlite_db - @app.before_request - def before_request(): - g.db = connect_db() + @app.teardown_appcontext + def close_connection(exception): + top = _app_ctx_stack.top + if hasattr(top, 'sqlite_db'): + top.sqlite_db.close() + +All the application needs to do in order to now use the database is having +an active application context (which is always true if there is an request +in flight) or to create an application context itself. At that point the +``get_db`` function can be used to get the current database connection. +Whenever the context is destroyed the database connection will be +terminated. + +Example:: + + @app.route('/') + def index(): + cur = get_db().cursor() + ... - @app.teardown_request - def teardown_request(exception): - if hasattr(g, 'db'): - g.db.close() .. note:: - Please keep in mind that the teardown request functions are always - executed, even if a before-request handler failed or was never - executed. Because of this we have to make sure here that the database - is there before we close it. + Please keep in mind that the teardown request and appcontext functions + are always executed, even if a before-request handler failed or was + never executed. Because of this we have to make sure here that the + database is there before we close it. Connect on Demand ----------------- -The downside of this approach is that this will only work if Flask -executed the before-request handlers for you. If you are attempting to -use the database from a script or the interactive Python shell you would -have to do something like this:: +The upside of this approach (connecting on first use) is that this will +only opening the connection if truly necessary. If you want to use this +code outside a request context you can use it in a Python shell by opening +the application context by hand:: - with app.test_request_context(): - app.preprocess_request() - # now you can use the g.db object - -In order to trigger the execution of the connection code. You won't be -able to drop the dependency on the request context this way, but you could -make it so that the application connects when necessary:: - - def get_connection(): - db = getattr(g, '_db', None) - if db is None: - db = g._db = connect_db() - return db - -Downside here is that you have to use ``db = get_connection()`` instead of -just being able to use ``g.db`` directly. + with app.app_context(): + # now you can use get_db() .. _easy-querying: @@ -66,16 +66,28 @@ Easy Querying Now in each request handling function you can access `g.db` to get the current open database connection. To simplify working with SQLite, a -helper function can be useful:: +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 can be used:: + def make_dicts(cursor, row): + return dict((cur.description[idx][0], value) + for idx, value in enumerate(row)) + + db.row_factory = make_dicts + +Additionally it is a good idea to provide a query function that combines +getting the cursor, executing and fetching the results:: + def query_db(query, args=(), one=False): - cur = g.db.execute(query, args) - rv = [dict((cur.description[idx][0], value) - for idx, value in enumerate(row)) for row in cur.fetchall()] + cur = get_db().execute(query, args) + rv = cur.fetchall() + cur.close() return (rv[0] if rv else None) if one else rv -This handy little function makes working with the database much more -pleasant than it is by just using the raw cursor and connection objects. +This handy little function in combination with a row factory makes working +with the database much more pleasant than it is by just using the raw +cursor and connection objects. Here is how you can use it:: @@ -105,10 +117,9 @@ Relational databases need schemas, so applications often ship a a function that creates the database based on that schema. This function can do that for you:: - from contextlib import closing - def init_db(): - with closing(connect_db()) as db: + with app.app_context(): + db = get_db() with app.open_resource('schema.sql') as f: db.cursor().executescript(f.read()) db.commit() diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 6f9b06fc..8526254d 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -11,9 +11,8 @@ """ from __future__ import with_statement from sqlite3 import dbapi2 as sqlite3 -from contextlib import closing from flask import Flask, request, session, g, redirect, url_for, abort, \ - render_template, flash + render_template, flash, _app_ctx_stack # configuration DATABASE = '/tmp/flaskr.db' @@ -28,35 +27,37 @@ app.config.from_object(__name__) app.config.from_envvar('FLASKR_SETTINGS', silent=True) -def connect_db(): - """Returns a new connection to the database.""" - return sqlite3.connect(app.config['DATABASE']) - - def init_db(): """Creates the database tables.""" - with closing(connect_db()) as db: + with app.app_context(): + db = get_db() with app.open_resource('schema.sql') as f: db.cursor().executescript(f.read()) db.commit() -@app.before_request -def before_request(): - """Make sure we are connected to the database each request.""" - g.db = connect_db() +def get_db(): + """Opens a new database connection if there is none yet for the + current application context. + """ + top = _app_ctx_stack.top + if not hasattr(top, 'sqlite_db'): + top.sqlite_db = sqlite3.connect(app.config['DATABASE']) + return top.sqlite_db -@app.teardown_request -def teardown_request(exception): +@app.teardown_appcontext +def close_db_connection(exception): """Closes the database again at the end of the request.""" - if hasattr(g, 'db'): - g.db.close() + top = _app_ctx_stack.top + if hasattr(top, 'sqlite_db'): + top.sqlite_db.close() @app.route('/') def show_entries(): - cur = g.db.execute('select title, text from entries order by id desc') + db = get_db() + cur = db.execute('select title, text from entries order by id desc') entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] return render_template('show_entries.html', entries=entries) @@ -65,9 +66,10 @@ def show_entries(): def add_entry(): if not session.get('logged_in'): abort(401) - g.db.execute('insert into entries (title, text) values (?, ?)', + db = get_db() + db.execute('insert into entries (title, text) values (?, ?)', [request.form['title'], request.form['text']]) - g.db.commit() + db.commit() flash('New entry was successfully posted') return redirect(url_for('show_entries')) @@ -95,4 +97,5 @@ def logout(): if __name__ == '__main__': + init_db() app.run() diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index fee023fd..a92da6af 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -13,9 +13,8 @@ import time from sqlite3 import dbapi2 as sqlite3 from hashlib import md5 from datetime import datetime -from contextlib import closing from flask import Flask, request, session, url_for, redirect, \ - render_template, abort, g, flash + render_template, abort, g, flash, _app_ctx_stack from werkzeug import check_password_hash, generate_password_hash @@ -31,14 +30,29 @@ app.config.from_object(__name__) app.config.from_envvar('MINITWIT_SETTINGS', silent=True) -def connect_db(): - """Returns a new connection to the database.""" - return sqlite3.connect(app.config['DATABASE']) +def get_db(): + """Opens a new database connection if there is none yet for the + current application context. + """ + top = _app_ctx_stack.top + if not hasattr(top, 'sqlite_db'): + top.sqlite_db = sqlite3.connect(app.config['DATABASE']) + top.sqlite_db.row_factory = sqlite3.Row + return top.sqlite_db + + +@app.teardown_appcontext +def close_database(exception): + """Closes the database again at the end of the request.""" + top = _app_ctx_stack.top + if hasattr(top, 'sqlite_db'): + top.sqlite_db.close() def init_db(): """Creates the database tables.""" - with closing(connect_db()) as db: + with app.app_context(): + db = get_db() with app.open_resource('schema.sql') as f: db.cursor().executescript(f.read()) db.commit() @@ -46,16 +60,15 @@ def init_db(): def query_db(query, args=(), one=False): """Queries the database and returns a list of dictionaries.""" - cur = g.db.execute(query, args) - rv = [dict((cur.description[idx][0], value) - for idx, value in enumerate(row)) for row in cur.fetchall()] + cur = get_db().execute(query, args) + rv = cur.fetchall() return (rv[0] if rv else None) if one else rv def get_user_id(username): """Convenience method to look up the id for a username.""" - rv = g.db.execute('select user_id from user where username = ?', - [username]).fetchone() + rv = query_db('select user_id from user where username = ?', + [username], one=True) return rv[0] if rv else None @@ -72,23 +85,12 @@ def gravatar_url(email, size=80): @app.before_request def before_request(): - """Make sure we are connected to the database each request and look - up the current user so that we know he's there. - """ - g.db = connect_db() g.user = None if 'user_id' in session: g.user = query_db('select * from user where user_id = ?', [session['user_id']], one=True) -@app.teardown_request -def teardown_request(exception): - """Closes the database again at the end of the request.""" - if hasattr(g, 'db'): - g.db.close() - - @app.route('/') def timeline(): """Shows a users timeline or if no user is logged in it will @@ -145,9 +147,10 @@ def follow_user(username): whom_id = get_user_id(username) if whom_id is None: abort(404) - g.db.execute('insert into follower (who_id, whom_id) values (?, ?)', - [session['user_id'], whom_id]) - g.db.commit() + db = get_db() + db.execute('insert into follower (who_id, whom_id) values (?, ?)', + [session['user_id'], whom_id]) + db.commit() flash('You are now following "%s"' % username) return redirect(url_for('user_timeline', username=username)) @@ -160,9 +163,10 @@ def unfollow_user(username): whom_id = get_user_id(username) if whom_id is None: abort(404) - g.db.execute('delete from follower where who_id=? and whom_id=?', - [session['user_id'], whom_id]) - g.db.commit() + db = get_db() + db.execute('delete from follower where who_id=? and whom_id=?', + [session['user_id'], whom_id]) + db.commit() flash('You are no longer following "%s"' % username) return redirect(url_for('user_timeline', username=username)) @@ -173,10 +177,11 @@ def add_message(): if 'user_id' not in session: abort(401) if request.form['text']: - g.db.execute('''insert into message (author_id, text, pub_date) - values (?, ?, ?)''', (session['user_id'], request.form['text'], - int(time.time()))) - g.db.commit() + db = get_db() + db.execute('''insert into message (author_id, text, pub_date) + values (?, ?, ?)''', (session['user_id'], request.form['text'], + int(time.time()))) + db.commit() flash('Your message was recorded') return redirect(url_for('timeline')) @@ -221,11 +226,12 @@ def register(): elif get_user_id(request.form['username']) is not None: error = 'The username is already taken' else: - g.db.execute('''insert into user ( - username, email, pw_hash) values (?, ?, ?)''', - [request.form['username'], request.form['email'], - generate_password_hash(request.form['password'])]) - g.db.commit() + db = get_db() + db.execute('''insert into user ( + username, email, pw_hash) values (?, ?, ?)''', + [request.form['username'], request.form['email'], + generate_password_hash(request.form['password'])]) + db.commit() flash('You were successfully registered and can login now') return redirect(url_for('login')) return render_template('register.html', error=error) @@ -245,4 +251,5 @@ app.jinja_env.filters['gravatar'] = gravatar_url if __name__ == '__main__': + init_db() app.run() From f6a5a7a0cc91ccd06e89ac0820dd6d7bdf2c2ef1 Mon Sep 17 00:00:00 2001 From: Corbin Simpson <MostAwesomeDude@gmail.com> Date: Thu, 11 Oct 2012 14:05:01 -0700 Subject: [PATCH 0257/2940] docs/deploying/wsgi-standalone: Add Twisted Web. I've been meaning to do this for quite some time, but I never got around to it. Hopefully this is neutral and useful enough to be included in the main docs. --- docs/deploying/wsgi-standalone.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/deploying/wsgi-standalone.rst b/docs/deploying/wsgi-standalone.rst index 74385813..7a3ef0a9 100644 --- a/docs/deploying/wsgi-standalone.rst +++ b/docs/deploying/wsgi-standalone.rst @@ -66,6 +66,29 @@ event loop:: .. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html .. _libevent: http://monkey.org/~provos/libevent/ +Twisted Web +----------- + +`Twisted Web`_ is the web server shipped with `Twisted`_, a mature, +non-blocking event-driven networking library. Twisted Web comes with a +standard WSGI container which can be controlled from the command line using +the ``twistd`` utility:: + + twistd web --wsgi myproject.app + +This example will run a Flask application called ``app`` from a module named +``myproject``. + +Twisted Web supports many flags and options, and the ``twistd`` utility does +as well; see ``twistd -h`` and ``twistd web -h`` for more information. For +example, to run a Twisted Web server in the foreground, on port 8080, with an +application from ``myproject``:: + + twistd -n web --port 8080 --wsgi myproject.app + +.. _Twisted: https://twistedmatrix.com/ +.. _Twisted Web: https://twistedmatrix.com/trac/wiki/TwistedWeb + .. _deploying-proxy-setups: Proxy Setups From 7ee40e9348fba36d57727d7aa58d802b191c0197 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer <markus@unterwaditzer.net> Date: Tue, 16 Oct 2012 21:56:51 +0200 Subject: [PATCH 0258/2940] Fix #611 Minor but confusing typo in tutorial. --- docs/tutorial/dbinit.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 79479397..6415cdaa 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -32,7 +32,7 @@ add the following lines to your existing imports in `flaskr.py`:: Next we can create a function called `init_db` that initializes the database. For this we can use the `connect_db` function we defined earlier. Just add that function below the `connect_db` function in -`flask.py`:: +`flaskr.py`:: def init_db(): with closing(connect_db()) as db: From a15c6c569ab40f0da2d18b1bd338bd03ddb024eb Mon Sep 17 00:00:00 2001 From: Mitchell Peabody <mitchell.peabody@qrclab.com> Date: Tue, 16 Oct 2012 16:57:57 -0400 Subject: [PATCH 0259/2940] The builder on github is using python 2.5, the views.py testsuite uses the with statement, and thus flask/testsuite/views.py requires from __future__ import with_statement at the beginning. --- flask/testsuite/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py index 350eb7f2..f09c1266 100644 --- a/flask/testsuite/views.py +++ b/flask/testsuite/views.py @@ -8,13 +8,13 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement import flask import flask.views import unittest from flask.testsuite import FlaskTestCase from werkzeug.http import parse_set_header - class ViewTestCase(FlaskTestCase): def common_test(self, app): From 275f830c8346c29af424b1ed2f2ffabee2408ec8 Mon Sep 17 00:00:00 2001 From: Mitchell Peabody <mitchell.peabody@qrclab.com> Date: Wed, 17 Oct 2012 11:56:39 -0400 Subject: [PATCH 0260/2940] There was a duplicated call to url_adapter.build(...) try: rv = url_adapter.build(endpoint, values, method=method, force_external=external) except BuildError, error: # We need to inject the values again so that the app callback can # deal with that sort of stuff. values['_external'] = external values['_anchor'] = anchor values['_method'] = method return appctx.app.handle_url_build_error(error, endpoint, values) rv = url_adapter.build(endpoint, values, method=method, force_external=external) If no exception was raised for url_adapter.build(...) then the same method call would be made after the try...except block. This is unnecessary. --- flask/helpers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 3f06c116..6aea45c6 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -295,8 +295,6 @@ def url_for(endpoint, **values): values['_method'] = method return appctx.app.handle_url_build_error(error, endpoint, values) - rv = url_adapter.build(endpoint, values, method=method, - force_external=external) if anchor is not None: rv += '#' + url_quote(anchor) return rv From 8339cb35082fc93d3a9c98616aed7fe0c1c642dc Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Thu, 18 Oct 2012 00:48:15 +0100 Subject: [PATCH 0261/2940] Added support for unicode json dumping. This fixes #535 --- CHANGES | 4 ++++ docs/config.rst | 10 ++++++++++ flask/app.py | 3 ++- flask/json.py | 31 +++++++++++++++++++++++-------- flask/testsuite/helpers.py | 13 +++++++++++++ 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index fd9d110c..6a1ee7c7 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,10 @@ Release date to be decided. - The config object is now available to the template as a real global and not through a context processor which makes it available even in imported templates by default. +- Added an option to generate non-ascii encoded JSON which should result + in less bytes being transmitted over the network. It's disabled by + default to not cause confusion with existing libraries that might expect + ``flask.json.dumps`` to return bytestrings by default. Version 0.9 ----------- diff --git a/docs/config.rst b/docs/config.rst index 7a32fb84..134fe36d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -142,6 +142,13 @@ The following configuration values are used internally by Flask: ``PREFERRED_URL_SCHEME`` The URL scheme that should be used for URL generation if no URL scheme is available. This defaults to ``http``. +``JSON_AS_ASCII`` By default Flask serialize object to + ascii-encoded JSON. If this is set to + ``False`` Flask will not encode to ASCII + and output strings as-is and return + unicode strings. ``jsonfiy`` will + automatically encode it in ``utf-8`` + then for transport for instance. ================================= ========================================= .. admonition:: More on ``SERVER_NAME`` @@ -184,6 +191,9 @@ The following configuration values are used internally by Flask: .. versionadded:: 0.9 ``PREFERRED_URL_SCHEME`` +.. versionadded:: 0.10 + ``JSON_AS_ASCII`` + Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 87ba933e..9d291b3a 100644 --- a/flask/app.py +++ b/flask/app.py @@ -274,7 +274,8 @@ class Flask(_PackageBoundObject): 'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, - 'PREFERRED_URL_SCHEME': 'http' + 'PREFERRED_URL_SCHEME': 'http', + 'JSON_AS_ASCII': True }) #: The rule object to use for URL rules created. This is used by diff --git a/flask/json.py b/flask/json.py index 1593a326..a85bdc63 100644 --- a/flask/json.py +++ b/flask/json.py @@ -68,20 +68,37 @@ 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) + if not current_app.config['JSON_AS_ASCII']: + kwargs.setdefault('ensure_ascii', False) + + +def _load_arg_defaults(kwargs): + """Inject default arguments for load functions.""" + if current_app: + kwargs.setdefault('cls', current_app.json_decoder) + + def dumps(obj, **kwargs): """Serialize ``obj`` to a JSON formatted ``str`` by using the application's configured encoder (:attr:`~flask.Flask.json_encoder`) if there is an application on the stack. + + This function can return ``unicode`` strings or ascii-only bytestrings by + default which coerce into unicode strings automatically. That behavior by + default is controlled by the ``JSON_AS_ASCII`` configuration variable + and can be overriden by the simplejson ``ensure_ascii`` parameter. """ - if current_app: - kwargs.setdefault('cls', current_app.json_encoder) + _dump_arg_defaults(kwargs) return _json.dumps(obj, **kwargs) def dump(obj, fp, **kwargs): """Like :func:`dumps` but writes into a file object.""" - if current_app: - kwargs.setdefault('cls', current_app.json_encoder) + _dump_arg_defaults(kwargs) return _json.dump(obj, fp, **kwargs) @@ -90,16 +107,14 @@ def loads(s, **kwargs): configured decoder (:attr:`~flask.Flask.json_decoder`) if there is an application on the stack. """ - if current_app: - kwargs.setdefault('cls', current_app.json_decoder) + _load_arg_defaults(kwargs) return _json.loads(s, **kwargs) def load(fp, **kwargs): """Like :func:`loads` but reads from a file object. """ - if current_app: - kwargs.setdefault('cls', current_app.json_decoder) + _load_arg_defaults(kwargs) return _json.load(fp, **kwargs) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index ee5312e0..31f0dcb4 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -79,6 +79,19 @@ class JSONTestCase(FlaskTestCase): self.assert_equal(rv.mimetype, 'application/json') self.assert_equal(flask.json.loads(rv.data), d) + def test_json_as_unicode(self): + app = flask.Flask(__name__) + + app.config['JSON_AS_ASCII'] = True + with app.app_context(): + rv = flask.json.dumps(u'\N{SNOWMAN}') + self.assert_equal(rv, '"\\u2603"') + + app.config['JSON_AS_ASCII'] = False + with app.app_context(): + rv = flask.json.dumps(u'\N{SNOWMAN}') + self.assert_equal(rv, u'"\u2603"') + def test_json_attr(self): app = flask.Flask(__name__) @app.route('/add', methods=['POST']) From af76dd0fd44301a6434d193626f8bf309eee8b26 Mon Sep 17 00:00:00 2001 From: Tony Narlock <tony@git-pull.com> Date: Fri, 19 Oct 2012 11:50:59 -0400 Subject: [PATCH 0262/2940] Highlight first field on page load, enter sends submit request, double quote to single quote. Submission focuses first field again --- examples/jqueryexample/templates/index.html | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/jqueryexample/templates/index.html b/examples/jqueryexample/templates/index.html index 0545516d..55d01631 100644 --- a/examples/jqueryexample/templates/index.html +++ b/examples/jqueryexample/templates/index.html @@ -2,20 +2,32 @@ {% block body %} <script type=text/javascript> $(function() { - $('a#calculate').bind('click', function() { + var submit_form = function(e) { $.getJSON($SCRIPT_ROOT + '/_add_numbers', { a: $('input[name="a"]').val(), b: $('input[name="b"]').val() }, function(data) { - $("#result").text(data.result); + $('#result').text(data.result); + $('input[name=a]').focus().select(); }); return false; + }; + + $('a#calculate').bind('click', submit_form); + + $('input[type=text]').bind('keydown', function(e) { + if (e.keyCode == 13) { + submit_form(e); + } }); + + $('input[name=a]').focus(); }); </script> <h1>jQuery Example</h1> -<p><input type=text size=5 name=a> + - <input type=text size=5 name=b> = - <span id=result>?</span> +<p> + <input type=text size=5 name=a> + + <input type=text size=5 name=b> = + <span id=result>?</span> <p><a href=# id=calculate>calculate server side</a> {% endblock %} From 1e39871d681bd41451cf8694cd67fffe017fa522 Mon Sep 17 00:00:00 2001 From: Quentin Roy <royque@gmail.com> Date: Sat, 20 Oct 2012 18:18:42 +0200 Subject: [PATCH 0263/2940] Add back path=None argument into find_module. This fixes #618 and revert #524. --- scripts/flaskext_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index 050fd7e0..cb0b436c 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -44,7 +44,7 @@ class ExtensionImporter(object): def install(self): sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] - def find_module(self, fullname): + def find_module(self, fullname, path=None): if fullname.startswith(self.prefix): return self From 2b885ce4dc3f6b2ea2707a39a8198a84d7ad3991 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 30 Oct 2012 14:47:17 +0000 Subject: [PATCH 0264/2940] Added better error reporting for unicode errors in sessions --- flask/debughelpers.py | 6 ++++++ flask/sessions.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index edf8c111..3ebd2f3e 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -10,6 +10,12 @@ """ +class UnexpectedUnicodeError(AssertionError, UnicodeError): + """Raised in places where we want some better error reporting for + unexpected unicode or binary data. + """ + + class DebugFilesKeyError(KeyError, AssertionError): """Raised from request.files during debugging. The idea is that it can provide a better error message than just a generic KeyError/BadRequest. diff --git a/flask/sessions.py b/flask/sessions.py index 46ce0830..bbf41ba0 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -66,6 +66,14 @@ class TaggedJSONSerializer(object): return {' d': http_date(value)} elif isinstance(value, dict): return dict((k, _tag(v)) for k, v in value.iteritems()) + elif isinstance(value, str): + try: + return unicode(value) + except UnicodeError: + raise UnexpectedUnicodeError(u'A byte string with ' + u'non-ASCII data was passed to the session system ' + u'which can only store unicode strings. Consider ' + u'base64 encoding your string (String was %r)' % value) return value return json.dumps(_tag(value), separators=(',', ':')) @@ -292,3 +300,6 @@ class SecureCookieSessionInterface(SessionInterface): response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure) + + +from flask.debughelpers import UnexpectedUnicodeError From 3c54f30c2b761e3decd56aa7ab8e5d00493238a1 Mon Sep 17 00:00:00 2001 From: soulseekah <gennady@kovshenin.com> Date: Sat, 3 Nov 2012 18:06:23 +0600 Subject: [PATCH 0265/2940] missing ' in example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Results in SyntaxError: EOL while scanning string literal --- docs/templating.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index 166f26aa..8a12cd6f 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -211,7 +211,7 @@ functions):: @app.context_processor def utility_processor(): def format_price(amount, currency=u'€'): - return u'{0:.2f}{1}.format(amount, currency) + return u'{0:.2f}{1}'.format(amount, currency) return dict(format_price=format_price) The context processor above makes the `format_price` function available to all From 82b29c09ac898042d589d852f1ef6f107fec9f71 Mon Sep 17 00:00:00 2001 From: Gennady Kovshenin <gennady@kovshenin.com> Date: Mon, 5 Nov 2012 06:00:46 +0600 Subject: [PATCH 0266/2940] Use sqlite3.Row factory in Flaskr As pointed out in issue #588 sqlite3.Row should be used instead of using casting to dict(). Also altered the "Easy Querying" Patterns example to include the more correct way to return rows as dicts. Did not touch Tutorial examples ("Views"), as these are not up to date with the current Flaskr code, and the "Show Entries" section points out the "Easy Querying" section on how to convert to a dict(). --- docs/patterns/sqlite3.rst | 4 ++++ examples/flaskr/flaskr.py | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 45bcc959..76fec0b2 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -76,6 +76,10 @@ dictionaries instead of tuples this can be used:: db.row_factory = make_dicts +Or even simpler:: + + db.row_factory = sqlite3.Row + Additionally it is a good idea to provide a query function that combines getting the cursor, executing and fetching the results:: diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 8526254d..0647fc7b 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -42,7 +42,10 @@ def get_db(): """ top = _app_ctx_stack.top if not hasattr(top, 'sqlite_db'): - top.sqlite_db = sqlite3.connect(app.config['DATABASE']) + sqlite_db = sqlite3.connect(app.config['DATABASE']) + sqlite_db.row_factory = sqlite3.Row + top.sqlite_db = sqlite_db + return top.sqlite_db @@ -58,7 +61,7 @@ def close_db_connection(exception): def show_entries(): db = get_db() cur = db.execute('select title, text from entries order by id desc') - entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()] + entries = cur.fetchall() return render_template('show_entries.html', entries=entries) From caefb67ccd0f8c1f5961c7e71f9658af98f9f009 Mon Sep 17 00:00:00 2001 From: Max Countryman <maxc@me.com> Date: Mon, 5 Nov 2012 15:31:07 -0800 Subject: [PATCH 0267/2940] correcting typo --- docs/patterns/streaming.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index ac232dcc..f5bff3ca 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -24,7 +24,7 @@ data and to then invoke that function and pass it to a response object:: yield ','.join(row) + '\n' return Response(generate(), mimetype='text/csv') -Each ``yield`` expression is directly sent to the browser. Now though +Each ``yield`` expression is directly sent to the browser. Note though that some WSGI middlewares might break streaming, so be careful there in debug environments with profilers and other things you might have enabled. From 30a51f2573423fce764b7f250e97e4b39471a480 Mon Sep 17 00:00:00 2001 From: Max Countryman <maxc@me.com> Date: Mon, 12 Nov 2012 16:58:38 -0800 Subject: [PATCH 0268/2940] correcting typo --- docs/appcontext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index a6b67001..1a8ed73c 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -18,7 +18,7 @@ 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. -On the contrast, during request handling, a couple of other rules exist: +In the contrast, during request handling, a couple of other rules exist: - while a request is active, the context local objects (:data:`flask.request` and others) point to the current request. From b6dac3812f84359e6e9fb3d23e6ea55d6f87561c Mon Sep 17 00:00:00 2001 From: Max Countryman <maxc@me.com> Date: Mon, 12 Nov 2012 17:00:51 -0800 Subject: [PATCH 0269/2940] really fixing it this time --- docs/appcontext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/appcontext.rst b/docs/appcontext.rst index 1a8ed73c..12f08ff2 100644 --- a/docs/appcontext.rst +++ b/docs/appcontext.rst @@ -18,7 +18,7 @@ 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 the contrast, during request handling, a couple of other rules exist: +In contrast, during request handling, a couple of other rules exist: - while a request is active, the context local objects (:data:`flask.request` and others) point to the current request. From be1ed3327126edcb485be85a8f2de58f23b6ab64 Mon Sep 17 00:00:00 2001 From: Iyra Gaura <iyra72@gmail.com> Date: Sat, 17 Nov 2012 18:13:14 +0000 Subject: [PATCH 0270/2940] Ended your paragraph tag. --- examples/flaskr/templates/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flaskr/templates/login.html b/examples/flaskr/templates/login.html index 6f70bb76..25fdc614 100644 --- a/examples/flaskr/templates/login.html +++ b/examples/flaskr/templates/login.html @@ -1,7 +1,7 @@ {% extends "layout.html" %} {% block body %} <h2>Login</h2> - {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} + {% if error %}<p class=error><strong>Error:</strong> {{ error }}</p>{% endif %} <form action="{{ url_for('login') }}" method=post> <dl> <dt>Username: From 160aa8078166f7b32015c6b3ad63a91c4500badc Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer <markus@unterwaditzer.net> Date: Mon, 10 Dec 2012 18:37:03 +0100 Subject: [PATCH 0271/2940] Added hint about print statements in CGI. Fix #646 --- docs/deploying/cgi.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/deploying/cgi.rst b/docs/deploying/cgi.rst index 1de9bd2c..17116be5 100644 --- a/docs/deploying/cgi.rst +++ b/docs/deploying/cgi.rst @@ -16,6 +16,10 @@ Engine`_, where execution happens in a CGI-like environment. not called because this will always start a local WSGI server which we do not want if we deploy that application to CGI / app engine. + With CGI, you will also have to make sure that your code does not contain + any ``print`` statements, or that ``sys.stdin`` is overridden by something + that doesn't write into the HTTP response. + Creating a `.cgi` file ---------------------- From a319516518bb39cb840529f41df713a6b4a6a563 Mon Sep 17 00:00:00 2001 From: Erik Rose <erik@mozilla.com> Date: Tue, 11 Dec 2012 14:11:27 -0800 Subject: [PATCH 0272/2940] Fix a typo in the deferred-callbacks docs. --- docs/patterns/deferredcallbacks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst index 917c5125..feab2579 100644 --- a/docs/patterns/deferredcallbacks.rst +++ b/docs/patterns/deferredcallbacks.rst @@ -19,7 +19,7 @@ instead. Sometimes however moving that code there is just not a very pleasant experience or makes code look very awkward. As an alternative possibility you can attach a bunch of callback functions -to the :data:`~flask.g` object and call then at the end of the request. +to the :data:`~flask.g` object and call them at the end of the request. This way you can defer code execution from anywhere in the application. From be0b5196bf5b11130ca1688c1aab2750afba8433 Mon Sep 17 00:00:00 2001 From: INADA Naoki <songofacandy@gmail.com> Date: Thu, 13 Dec 2012 19:03:54 +0900 Subject: [PATCH 0273/2940] Update docs/extensiondev.rst Use `current_app` instead of `self.app`. --- docs/extensiondev.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index d266e1a2..0615e4db 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -165,6 +165,7 @@ The Extension Code Here's the contents of the `flask_sqlite3.py` for copy/paste:: import sqlite3 + from flask import current_app # Find the stack on which we want to store the database connection. # Starting with Flask 0.9, the _app_ctx_stack is the correct one, @@ -178,11 +179,9 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste:: class SQLite3(object): def __init__(self, app=None): + self.app = app if app is not None: - self.app = app - self.init_app(self.app) - else: - self.app = None + self.init_app(app) def init_app(self, app): app.config.setdefault('SQLITE3_DATABASE', ':memory:') @@ -194,7 +193,7 @@ Here's the contents of the `flask_sqlite3.py` for copy/paste:: app.teardown_request(self.teardown) def connect(self): - return sqlite3.connect(self.app.config['SQLITE3_DATABASE']) + return sqlite3.connect(current_app.config['SQLITE3_DATABASE']) def teardown(self, exception): ctx = stack.top From 61d43c7f1269d9dafb40d4483cf88c7e32e95edc Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Wed, 19 Dec 2012 15:46:18 +0100 Subject: [PATCH 0274/2940] Removed unnecessary end-tags and fixed some broken html --- .../simple_page/templates/pages/hello.html | 4 +-- .../simple_page/templates/pages/index.html | 4 +-- .../simple_page/templates/pages/layout.html | 31 ++++++++----------- .../simple_page/templates/pages/world.html | 5 ++- examples/flaskr/templates/login.html | 2 +- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/examples/blueprintexample/simple_page/templates/pages/hello.html b/examples/blueprintexample/simple_page/templates/pages/hello.html index 7fca6668..7e4a624d 100644 --- a/examples/blueprintexample/simple_page/templates/pages/hello.html +++ b/examples/blueprintexample/simple_page/templates/pages/hello.html @@ -1,5 +1,5 @@ {% extends "pages/layout.html" %} {% block body %} - Hello -{% endblock %} \ No newline at end of file + Hello +{% endblock %} diff --git a/examples/blueprintexample/simple_page/templates/pages/index.html b/examples/blueprintexample/simple_page/templates/pages/index.html index 0ca3ffe2..b8d92da4 100644 --- a/examples/blueprintexample/simple_page/templates/pages/index.html +++ b/examples/blueprintexample/simple_page/templates/pages/index.html @@ -1,5 +1,5 @@ {% extends "pages/layout.html" %} {% block body %} - Blueprint example page -{% endblock %} \ No newline at end of file + Blueprint example page +{% endblock %} diff --git a/examples/blueprintexample/simple_page/templates/pages/layout.html b/examples/blueprintexample/simple_page/templates/pages/layout.html index 2efccb95..e74a5871 100644 --- a/examples/blueprintexample/simple_page/templates/pages/layout.html +++ b/examples/blueprintexample/simple_page/templates/pages/layout.html @@ -3,23 +3,18 @@ <div class=page> <h1>This is blueprint example</h1> <p> - A simple page blueprint is registered under / and /pages<br/> - you can access it using this urls: - <ul> - <li><a href="{{ url_for('simple_page.show', page='hello') }}">/hello</a></li> - <li><a href="{{ url_for('simple_page.show', page='world') }}">/world</a></li> - </ul> - </p> + A simple page blueprint is registered under / and /pages + you can access it using this urls: + <ul> + <li><a href="{{ url_for('simple_page.show', page='hello') }}">/hello</a> + <li><a href="{{ url_for('simple_page.show', page='world') }}">/world</a> + </ul> <p> - Also you can register the same blueprint under another path - <ul> - <li><a href="/pages/hello">/pages/hello</a></li> - <li><a href="/pages/world">/pages/world</a></li> - </ul> - </p> - + Also you can register the same blueprint under another path + <ul> + <li><a href="/pages/hello">/pages/hello</a> + <li><a href="/pages/world">/pages/world</a> + </ul> - - {% block body %} - {% endblock %} -</div> \ No newline at end of file + {% block body %}{% endblock %} +</div> diff --git a/examples/blueprintexample/simple_page/templates/pages/world.html b/examples/blueprintexample/simple_page/templates/pages/world.html index bdb5b16b..9fa2880a 100644 --- a/examples/blueprintexample/simple_page/templates/pages/world.html +++ b/examples/blueprintexample/simple_page/templates/pages/world.html @@ -1,5 +1,4 @@ {% extends "pages/layout.html" %} - {% block body %} - World -{% endblock %} \ No newline at end of file + World +{% endblock %} diff --git a/examples/flaskr/templates/login.html b/examples/flaskr/templates/login.html index 25fdc614..6f70bb76 100644 --- a/examples/flaskr/templates/login.html +++ b/examples/flaskr/templates/login.html @@ -1,7 +1,7 @@ {% extends "layout.html" %} {% block body %} <h2>Login</h2> - {% if error %}<p class=error><strong>Error:</strong> {{ error }}</p>{% endif %} + {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %} <form action="{{ url_for('login') }}" method=post> <dl> <dt>Username: From c549e583c4cab9b657cf91544375031a64c79733 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer <markus@unterwaditzer.net> Date: Mon, 17 Dec 2012 21:32:59 +0100 Subject: [PATCH 0275/2940] Fixing my own pull request #647 Wrote "stdin" instead of "stdout". --- docs/deploying/cgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/cgi.rst b/docs/deploying/cgi.rst index 17116be5..3225bc58 100644 --- a/docs/deploying/cgi.rst +++ b/docs/deploying/cgi.rst @@ -17,7 +17,7 @@ Engine`_, where execution happens in a CGI-like environment. we do not want if we deploy that application to CGI / app engine. With CGI, you will also have to make sure that your code does not contain - any ``print`` statements, or that ``sys.stdin`` is overridden by something + any ``print`` statements, or that ``sys.stdout`` is overridden by something that doesn't write into the HTTP response. Creating a `.cgi` file From 1949c4a9abc174bf29620f6dd8ceab9ed3ace2eb Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Fri, 21 Dec 2012 11:45:42 +0100 Subject: [PATCH 0276/2940] flask.g is now on the app context and not the request context --- CHANGES | 5 +++++ docs/api.rst | 4 ++++ docs/upgrading.rst | 7 +++++++ flask/app.py | 22 +++++++++++++++++++--- flask/ctx.py | 11 +++++++++-- flask/globals.py | 16 ++++++++++++---- flask/templating.py | 15 ++++++++------- flask/testsuite/appctx.py | 6 +++--- flask/testsuite/basic.py | 5 ++++- 9 files changed, 71 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index 6a1ee7c7..ddba0928 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,11 @@ Release date to be decided. in less bytes being transmitted over the network. It's disabled by default to not cause confusion with existing libraries that might expect ``flask.json.dumps`` to return bytestrings by default. +- ``flask.g`` is now stored on the app context instead of the request + context. +- ``flask.Flask.request_globals_class`` got renamed to + ``flask.Flask.app_ctx_globals_class`` which is a better name to what it + does since 0.10. Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index dbd1877e..12e4b966 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -258,6 +258,10 @@ thing, like it does for :class:`request` and :class:`session`. Just store on this whatever you want. For example a database connection or the user that is currently logged in. + Starting with Flask 0.10 this is stored on the application context and + no longer on the request context which means it becomes available if + only the application context is bound and not yet a request. + This is a proxy. See :ref:`notes-on-proxies` for more information. diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 1f67df05..1d9239f5 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -36,6 +36,13 @@ extensions for tuples and strings with HTML markup. In order to not break people's sessions it is possible to continue using the old session system by using the `Flask-OldSessions_` extension. +Flask also started storing the :data:`flask.g` object on the application +context instead of the request context. This change should be transparent +for you but it means that you now can store things on the ``g`` object +when there is no request context yet but an application context. The old +``flask.Flask.request_globals_class`` attribute was renamed to +:attr:`flask.Flask.app_ctx_globals_class`. + .. _Flask-OldSessions: http://packages.python.org/Flask-OldSessions/ Version 0.9 diff --git a/flask/app.py b/flask/app.py index 9d291b3a..902c2ba8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -28,7 +28,7 @@ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ from . import json from .wrappers import Request, Response from .config import ConfigAttribute, Config -from .ctx import RequestContext, AppContext, _RequestGlobals +from .ctx import RequestContext, AppContext, _AppCtxGlobals from .globals import _request_ctx_stack, request from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module @@ -157,8 +157,24 @@ class Flask(_PackageBoundObject): #: 3. Return None instead of AttributeError on expected attributes. #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. #: - #: .. versionadded:: 0.9 - request_globals_class = _RequestGlobals + #: In Flask 0.9 this property was called `request_globals_class` but it + #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the + #: flask.g object is not application context scoped. + #: + #: .. versionadded:: 0.10 + app_ctx_globals_class = _AppCtxGlobals + + # Backwards compatibility support + def _get_request_globals_class(self): + return self.app_ctx_globals_class + def _set_request_globals_class(self, value): + from warnings import warn + warn(DeprecationWarning('request_globals_class attribute is now ' + 'called app_ctx_globals_class')) + self.app_ctx_globals_class = value + request_globals_class = property(_get_request_globals_class, + _set_request_globals_class) + del _get_request_globals_class, _set_request_globals_class #: The debug flag. Set this to `True` to enable debugging of the #: application. In debug mode the debugger will kick in when an unhandled diff --git a/flask/ctx.py b/flask/ctx.py index 84e96575..a1d9af62 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -17,7 +17,7 @@ from .globals import _request_ctx_stack, _app_ctx_stack from .module import blueprint_is_module -class _RequestGlobals(object): +class _AppCtxGlobals(object): """A plain object.""" pass @@ -101,6 +101,7 @@ class AppContext(object): def __init__(self, app): self.app = app self.url_adapter = app.create_url_adapter(None) + self.g = app.app_ctx_globals_class() # Like request context, app contexts can be pushed multiple times # but there a basic "refcount" is enough to track them. @@ -164,7 +165,6 @@ class RequestContext(object): self.app = app self.request = app.request_class(environ) self.url_adapter = app.create_url_adapter(self.request) - self.g = app.request_globals_class() self.flashes = None self.session = None @@ -195,6 +195,13 @@ class RequestContext(object): if bp is not None and blueprint_is_module(bp): self.request._is_old_module = True + def _get_g(self): + return _app_ctx_stack.top.g + def _set_g(self, value): + _app_ctx_stack.top.g = value + g = property(_get_g, _set_g) + del _get_g, _set_g + def match_request(self): """Can be overridden by a subclass to hook into the matching of the request. diff --git a/flask/globals.py b/flask/globals.py index f6d62485..67d41f5c 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -13,13 +13,21 @@ from functools import partial from werkzeug.local import LocalStack, LocalProxy -def _lookup_object(name): + +def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError('working outside of request context') return getattr(top, name) +def _lookup_app_object(name): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError('working outside of application context') + return getattr(top, name) + + def _find_app(): top = _app_ctx_stack.top if top is None: @@ -31,6 +39,6 @@ def _find_app(): _request_ctx_stack = LocalStack() _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) -request = LocalProxy(partial(_lookup_object, 'request')) -session = LocalProxy(partial(_lookup_object, 'session')) -g = LocalProxy(partial(_lookup_object, 'g')) +request = LocalProxy(partial(_lookup_req_object, 'request')) +session = LocalProxy(partial(_lookup_req_object, 'session')) +g = LocalProxy(partial(_lookup_app_object, 'g')) diff --git a/flask/templating.py b/flask/templating.py index 2bac22e9..2cc09c4d 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -22,13 +22,14 @@ def _default_template_ctx_processor(): `session` and `g`. """ reqctx = _request_ctx_stack.top - if reqctx is None: - return {} - return dict( - request=reqctx.request, - session=reqctx.session, - g=reqctx.g - ) + appctx = _app_ctx_stack.top + rv = {} + if appctx is not None: + rv['g'] = appctx.g + if reqctx is not None: + rv['request'] = reqctx.request + rv['session'] = reqctx.session + return rv class Environment(BaseEnvironment): diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index 6454389e..aa71e11e 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -65,13 +65,13 @@ class AppContextTestCase(FlaskTestCase): self.assert_equal(cleanup_stuff, [None]) - def test_custom_request_globals_class(self): + def test_custom_app_ctx_globals_class(self): class CustomRequestGlobals(object): def __init__(self): self.spam = 'eggs' app = flask.Flask(__name__) - app.request_globals_class = CustomRequestGlobals - with app.test_request_context(): + app.app_ctx_globals_class = CustomRequestGlobals + with app.app_context(): self.assert_equal( flask.render_template_string('{{ g.spam }}'), 'eggs') diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 964d2c18..efee244a 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -1104,8 +1104,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): c.get('/fail') self.assert_(flask._request_ctx_stack.top is not None) - flask._request_ctx_stack.pop() + self.assert_(flask._app_ctx_stack.top is not None) + # implicit appctx disappears too + flask._request_ctx_stack.top.pop() self.assert_(flask._request_ctx_stack.top is None) + self.assert_(flask._app_ctx_stack.top is None) class ContextTestCase(FlaskTestCase): From 2af0ffaef63dbd6031d889d28e4d60794becb7b6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Fri, 21 Dec 2012 11:47:27 +0100 Subject: [PATCH 0277/2940] Added proxies to template context --- CHANGES | 4 ++++ flask/app.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index ddba0928..044dae44 100644 --- a/CHANGES +++ b/CHANGES @@ -39,6 +39,10 @@ Release date to be decided. - ``flask.Flask.request_globals_class`` got renamed to ``flask.Flask.app_ctx_globals_class`` which is a better name to what it does since 0.10. +- `request`, `session` and `g` are now also added as proxies to the template + context which makes them available in imported templates. One has to be + very careful with those though because usage outside of macros might + cause caching. Version 0.9 ----------- diff --git a/flask/app.py b/flask/app.py index 902c2ba8..162a1d44 100644 --- a/flask/app.py +++ b/flask/app.py @@ -29,7 +29,7 @@ from . import json from .wrappers import Request, Response from .config import ConfigAttribute, Config from .ctx import RequestContext, AppContext, _AppCtxGlobals -from .globals import _request_ctx_stack, request +from .globals import _request_ctx_stack, request, session, g from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module from .templating import DispatchingJinjaLoader, Environment, \ @@ -663,7 +663,13 @@ class Flask(_PackageBoundObject): rv.globals.update( url_for=url_for, get_flashed_messages=get_flashed_messages, - config=self.config + config=self.config, + # request, session and g are normally added with the + # context processor for efficiency reasons but for imported + # templates we also want the proxies in there. + request=request, + session=session, + g=g ) rv.filters['tojson'] = json.htmlsafe_dumps return rv From cc82feb084092bf8212d0fb3a58386841f2294f6 Mon Sep 17 00:00:00 2001 From: oliversong <oliversong@tumblr.com> Date: Wed, 26 Dec 2012 00:05:18 -0500 Subject: [PATCH 0278/2940] Changing string to text in schema files --- docs/tutorial/schema.rst | 4 ++-- examples/flaskr/schema.sql | 4 ++-- examples/minitwit/schema.sql | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/tutorial/schema.rst b/docs/tutorial/schema.rst index c078667e..e4aa2f59 100644 --- a/docs/tutorial/schema.rst +++ b/docs/tutorial/schema.rst @@ -13,8 +13,8 @@ the just created `flaskr` folder: drop table if exists entries; create table entries ( id integer primary key autoincrement, - title string not null, - text string not null + title text not null, + text text not null ); This schema consists of a single table called `entries` and each row in diff --git a/examples/flaskr/schema.sql b/examples/flaskr/schema.sql index 970cca77..dbb06319 100644 --- a/examples/flaskr/schema.sql +++ b/examples/flaskr/schema.sql @@ -1,6 +1,6 @@ drop table if exists entries; create table entries ( id integer primary key autoincrement, - title string not null, - text string not null + title text not null, + text text not null ); diff --git a/examples/minitwit/schema.sql b/examples/minitwit/schema.sql index b64afbed..b272adc8 100644 --- a/examples/minitwit/schema.sql +++ b/examples/minitwit/schema.sql @@ -1,9 +1,9 @@ drop table if exists user; create table user ( user_id integer primary key autoincrement, - username string not null, - email string not null, - pw_hash string not null + username text not null, + email text not null, + pw_hash text not null ); drop table if exists follower; @@ -16,6 +16,6 @@ drop table if exists message; create table message ( message_id integer primary key autoincrement, author_id integer not null, - text string not null, + text text not null, pub_date integer ); From e3e4f4c2b88ecc32babc714fcc345a978aeee2f0 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz <me@kennethreitz.com> Date: Mon, 31 Dec 2012 17:55:39 -0500 Subject: [PATCH 0279/2940] 2013 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index dc01ee1a..3c590329 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 by Armin Ronacher and contributors. See AUTHORS +Copyright (c) 2013 by Armin Ronacher and contributors. See AUTHORS for more details. Some rights reserved. From ff2e8449ffbb37cb8ebf4d7575c0e9102c78e772 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz <me@kennethreitz.com> Date: Mon, 31 Dec 2012 18:00:58 -0500 Subject: [PATCH 0280/2940] 2013 --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 30df7147..c971a57d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,7 +43,7 @@ master_doc = 'index' # General information about the project. project = u'Flask' -copyright = u'2012, Armin Ronacher' +copyright = u'2013, Armin Ronacher' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 7a3d74f19f98b69e3047d0ed0085b5a1c9f806fc Mon Sep 17 00:00:00 2001 From: Baiju Muthukadan <baiju.m.mail@gmail.com> Date: Fri, 4 Jan 2013 14:37:13 +0530 Subject: [PATCH 0281/2940] WSGI specification has finalized for Python 3 Mention Python 3.x is not supported --- docs/installation.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 8dcae5a7..5e4673dd 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -14,9 +14,7 @@ could do that, but the most kick-ass method is virtualenv, so let's have a look at that first. You will need Python 2.5 or higher to get started, so be sure to have an -up-to-date Python 2.x installation. At the time of writing, the WSGI -specification has not yet been finalized for Python 3, so Flask cannot support -the 3.x series of Python. +up-to-date Python 2.x installation. Python 3.x is not supported. .. _virtualenv: From 2b30900e2c007cf39b3d73a8ad53afe2f29a3a2c Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer <markus@unterwaditzer.net> Date: Thu, 8 Nov 2012 20:57:17 +0100 Subject: [PATCH 0282/2940] Fix #623 --- flask/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 6aea45c6..e8d72bf4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -247,8 +247,9 @@ def url_for(endpoint, **values): appctx = _app_ctx_stack.top reqctx = _request_ctx_stack.top if appctx is None: - raise RuntimeError('Attempted to generate a URL with the application ' - 'context being pushed. This has to be executed ') + raise RuntimeError('Attempted to generate a URL without the ' + 'application context being pushed. This has to be ' + 'executed when application context is available.') # If request specific information is available we have some extra # features that support "relative" urls. From b4fc4412e8bbdb359d1f5b9dd22a95326e73b896 Mon Sep 17 00:00:00 2001 From: Trung Ly <trungly@gmail.com> Date: Mon, 14 Jan 2013 14:53:06 -0800 Subject: [PATCH 0283/2940] Update docs/patterns/deferredcallbacks.rst fix example code in Deferred Callback docs: don't set response upon executing callback --- docs/patterns/deferredcallbacks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst index feab2579..83d4fb49 100644 --- a/docs/patterns/deferredcallbacks.rst +++ b/docs/patterns/deferredcallbacks.rst @@ -49,7 +49,7 @@ this the following function needs to be registered as @app.after_request def call_after_request_callbacks(response): for callback in getattr(g, 'after_request_callbacks', ()): - response = callback(response) + callback(response) return response From f1537a9d7a1e5ab53e38166a2a8fd7bad9278c18 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 21 Jan 2013 17:44:32 +0000 Subject: [PATCH 0284/2940] Always trap proxy exceptions --- CHANGES | 2 ++ flask/app.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 044dae44..7f3374a1 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,8 @@ Release date to be decided. context which makes them available in imported templates. One has to be very careful with those though because usage outside of macros might cause caching. +- Flask will no longer invoke the wrong error handlers if a proxy + exception is passed through. Version 0.9 ----------- diff --git a/flask/app.py b/flask/app.py index 162a1d44..5d87b1e8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1327,8 +1327,12 @@ class Flask(_PackageBoundObject): # ensure not to trash sys.exc_info() at that point in case someone # wants the traceback preserved in handle_http_exception. Of course # we cannot prevent users from trashing it themselves in a custom - # trap_http_exception method so that's their fault then. - if isinstance(e, HTTPException) and not self.trap_http_exception(e): + # trap_http_exception method so that's their fault then. Proxy + # exceptions generally must always be trapped (filtered out by + # e.code == None) + if isinstance(e, HTTPException) \ + and e.code is not None \ + and not self.trap_http_exception(e): return self.handle_http_exception(e) blueprint_handlers = () From 61d3bbf1d237cd8f03e37af88ad4a6b59f27d4e1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 21 Jan 2013 17:55:07 +0000 Subject: [PATCH 0285/2940] Fixed last commit and added test --- flask/app.py | 12 ++++++------ flask/testsuite/regression.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/flask/app.py b/flask/app.py index 5d87b1e8..f6d40be2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1283,6 +1283,10 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.3 """ handlers = self.error_handler_spec.get(request.blueprint) + # Proxy exceptions don't have error codes. We want to always return + # those unchanged as errors + if e.code is None: + return e if handlers and e.code in handlers: handler = handlers[e.code] else: @@ -1327,12 +1331,8 @@ class Flask(_PackageBoundObject): # ensure not to trash sys.exc_info() at that point in case someone # wants the traceback preserved in handle_http_exception. Of course # we cannot prevent users from trashing it themselves in a custom - # trap_http_exception method so that's their fault then. Proxy - # exceptions generally must always be trapped (filtered out by - # e.code == None) - if isinstance(e, HTTPException) \ - and e.code is not None \ - and not self.trap_http_exception(e): + # trap_http_exception method so that's their fault then. + if isinstance(e, HTTPException) and not self.trap_http_exception(e): return self.handle_http_exception(e) blueprint_handlers = () diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py index 87a6289b..34fdefc0 100644 --- a/flask/testsuite/regression.py +++ b/flask/testsuite/regression.py @@ -86,7 +86,32 @@ class MemoryTestCase(FlaskTestCase): safe_join('/foo', '..') +class ExceptionTestCase(FlaskTestCase): + + def test_aborting(self): + class Foo(Exception): + whatever = 42 + app = flask.Flask(__name__) + app.testing = True + @app.errorhandler(Foo) + def handle_foo(e): + return str(e.whatever) + @app.route('/') + def index(): + raise flask.abort(flask.redirect(flask.url_for('test'))) + @app.route('/test') + def test(): + raise Foo() + + with app.test_client() as c: + rv = c.get('/') + self.assertEqual(rv.headers['Location'], 'http://localhost/test') + rv = c.get('/test') + self.assertEqual(rv.data, '42') + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(MemoryTestCase)) + suite.addTest(unittest.makeSuite(ExceptionTestCase)) return suite From b5069d07a24a3c3a54fb056aa6f4076a0e7088c7 Mon Sep 17 00:00:00 2001 From: Max Countryman <max.countryman@gmail.com> Date: Thu, 17 Jan 2013 15:08:45 -0800 Subject: [PATCH 0286/2940] adding `_scheme` parameter to `url_for` In order to better facilitate generation of URLs that make use of an HTTPS URL scheme this patch adds a parameter with this specific purpose in mind. To achieve this we explicitly pass in a param, `_scheme='https'`, and then set the `url_scheme` attribute of our `MapAdapter` instance appropriately. Importantly, `_external=True` must be set in order for this to work properly. As such, failure to do so results in a `ValueError` being raised. --- flask/helpers.py | 12 ++++++++++++ flask/testsuite/helpers.py | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index 6aea45c6..a1c09cc5 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -229,6 +229,9 @@ def url_for(endpoint, **values): that this is for building URLs outside the current application, and not for handling 404 NotFound errors. + .. versionadded:: 0.10 + The `_scheme` parameter was added. + .. versionadded:: 0.9 The `_anchor` and `_method` parameters were added. @@ -241,6 +244,8 @@ def url_for(endpoint, **values): :param _external: if set to `True`, an absolute URL is generated. Server address can be changed via `SERVER_NAME` configuration variable which defaults to `localhost`. + :param _scheme: a string specifying the desired URL scheme. The `_external` + parameter must be set to `True` or a `ValueError` is raised. :param _anchor: if provided this is added as anchor to the URL. :param _method: if provided this explicitly specifies an HTTP method. """ @@ -283,7 +288,14 @@ def url_for(endpoint, **values): anchor = values.pop('_anchor', None) method = values.pop('_method', None) + scheme = values.pop('_scheme', None) appctx.app.inject_url_defaults(endpoint, values) + + if scheme is not None: + if not external: + raise ValueError('When specifying _scheme, _external must be True') + url_adapter.url_scheme = scheme + try: rv = url_adapter.build(endpoint, values, method=method, force_external=external) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 31f0dcb4..fdf2d89f 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -397,6 +397,28 @@ class LoggingTestCase(FlaskTestCase): self.assert_equal(flask.url_for('index', _anchor='x y'), '/#x%20y') + def test_url_for_with_scheme(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return '42' + with app.test_request_context(): + self.assert_equal(flask.url_for('index', + _external=True, + _scheme='https'), + 'https://localhost/') + + def test_url_for_with_scheme_not_external(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return '42' + with app.test_request_context(): + self.assert_raises(ValueError, + flask.url_for, + 'index', + _scheme='https') + def test_url_with_method(self): from flask.views import MethodView app = flask.Flask(__name__) From fb6dee3639b5fad1800472cb5258d2603408e732 Mon Sep 17 00:00:00 2001 From: schneems <richard.schneeman@gmail.com> Date: Thu, 24 Jan 2013 17:49:55 -0600 Subject: [PATCH 0287/2940] update README to markdown --- README | 55 ------------------------------------------------------ README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 55 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index 7297f5db..00000000 --- a/README +++ /dev/null @@ -1,55 +0,0 @@ - // Flask // - - web development, one drop at a time - - - ~ What is Flask? - - Flask is a microframework for Python based on Werkzeug - and Jinja2. It's intended for getting started very quickly - and was developed with best intentions in mind. - - ~ Is it ready? - - It's still not 1.0 but it's shaping up nicely and is - already widely used. Consider the API to slightly - improve over time but we don't plan on breaking it. - - ~ What do I need? - - Jinja 2.4 and Werkzeug 0.7 or later. - `pip` or `easy_install` will install them for you if you do - `pip install Flask`. I encourage you to use a virtualenv. - Check the docs for complete installation and usage - instructions. - - ~ Where are the docs? - - Go to http://flask.pocoo.org/docs/ for a prebuilt version - of the current documentation. Otherwise build them yourself - from the sphinx sources in the docs folder. - - ~ Where are the tests? - - Good that you're asking. The tests are in the - flask/testsuite package. To run the tests use the - `run-tests.py` file: - - $ python run-tests.py - - If it's not enough output for you, you can use the - `--verbose` flag: - - $ python run-tests.py --verbose - - If you just want one particular testcase to run you can - provide it on the command line: - - $ python run-tests.py test_to_run - - ~ Where can I get help? - - Either use the #pocoo IRC channel on irc.freenode.net or - ask on the mailinglist: http://flask.pocoo.org/mailinglist/ - - See http://flask.pocoo.org/community/ for more resources. diff --git a/README.md b/README.md new file mode 100644 index 00000000..90084361 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Flask + +Web development, one drop at a time + + +## What is Flask? + +Flask is a microframework for Python based on Werkzeug +and Jinja2. It's intended for getting started very quickly +and was developed with best intentions in mind. + +## Is it ready? + +It's still not 1.0 but it's shaping up nicely and is +already widely used. Consider the API to slightly +improve over time but we don't plan on breaking it. + +## What do I need? + +Jinja 2.4 and Werkzeug 0.7 or later. +`pip` or `easy_install` will install them for you if you do +`pip install Flask`. I encourage you to use a virtualenv. +Check the docs for complete installation and usage +instructions. + +## Where are the docs? + +Go to http://flask.pocoo.org/docs/ for a prebuilt version +of the current documentation. Otherwise build them yourself +from the sphinx sources in the docs folder. + +## Where are the tests? + +Good that you're asking. The tests are in the +flask/testsuite package. To run the tests use the +`run-tests.py` file: + + $ python run-tests.py + +If it's not enough output for you, you can use the +`--verbose` flag: + + $ python run-tests.py --verbose + +If you just want one particular testcase to run you can +provide it on the command line: + + $ python run-tests.py test_to_run + +## Where can I get help? + +Either use the #pocoo IRC channel on irc.freenode.net or +ask on the mailinglist: http://flask.pocoo.org/mailinglist/ + +See http://flask.pocoo.org/community/ for more resources. + From 05f162329d425a3037c10cebfcdf941ce2802ca7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 27 Jan 2013 00:38:25 +0000 Subject: [PATCH 0288/2940] Added celery pattern --- docs/patterns/celery.rst | 92 ++++++++++++++++++++++++++++++++++++++++ docs/patterns/index.rst | 1 + 2 files changed, 93 insertions(+) create mode 100644 docs/patterns/celery.rst diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst new file mode 100644 index 00000000..c7cd3922 --- /dev/null +++ b/docs/patterns/celery.rst @@ -0,0 +1,92 @@ +Celery Based Background Tasks +============================= + +Celery is a task queue for Python with batteries included. It used to +have a Flask integration but it became unnecessary after some +restructuring of the internals of Celery with Version 3. This guide fills +in the blanks in how to properly use Celery with Flask but assumes that +you generally already read the `First Steps with Celery +<http://docs.celeryproject.org/en/master/getting-started/first-steps-with-celery.html>`_ +guide in the official Celery documentation. + +Installing Celery +----------------- + +Celery is on the Python Package Index (PyPI), so it can be installed with +standard Python tools like ``pip`` or ``easy_install``:: + + $ pip install celery + +Configuring Celery +------------------ + +The first thing you need is a Celery instance, this is called the celery +application. It serves the same purpose as the :class:`~flask.Flask` +object in Flask, just for Celery. Since this instance is used as the +entry-point for everything you want to do in Celery, like creating tasks +and managing workers, it must be possible for other modules to import it. + +For instance you can place this in a ``tasks`` module. While you can use +Celery without any reconfiguration with Flask, it becomes a bit nicer by +subclassing tasks and adding support for Flask's application contexts and +hooking it up with the Flask configuration. + +This is all that is necessary to properly integrate Celery with Flask:: + + from celery import Celery + + def make_celery(app): + celery = Celery(app.import_name, broker=app.config['CELERY_BROKER_URL']) + celery.conf.update(app.config) + TaskBase = celery.Task + class ContextTask(TaskBase): + abstract = True + def __call__(self, *args, **kwargs): + with app.app_context(): + return TaskBase.__call__(self, *args, **kwargs) + celery.Task = ContextTask + return celery + +The function creates a new Celery object, configures it with the broker +from the application config, updates the rest of the Celery config from +the Flask config and then creates a subclass of the task that wraps the +task execution in an application context. + +Minimal Example +--------------- + +With what we have above this is the minimal example of using Celery with +Flask:: + + from flask import Flask + + app = Flask(__name__) + app.config.update( + CELERY_BROKER_URL='redis://localhost:6379', + CELERY_RESULT_BACKEND='redis://localhost:6379' + ) + celery = make_celery(app) + + + @celery.task() + def add_together(a, b): + return a + b + +This task can now be called in the background: + +>>> result = add_together.delay(23, 42) +>>> result.wait() +65 + +Running the Celery Worker +------------------------- + +Now if you jumped in and already executed the above code you will be +disappointed to learn that your ``.wait()`` will never actually return. +That's because you also need to run celery. You can do that by running +celery as a worker:: + + $ celery -A your_application worker + +The ``your_application`` string has to point to your application's package +or module that creates the `celery` object. diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 4a09340f..8a9bf1ca 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -39,3 +39,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_. deferredcallbacks methodoverrides requestchecksum + celery From 3b393f89f66733f7824a50b33b93eae9999812ad Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 27 Jan 2013 00:46:19 +0000 Subject: [PATCH 0289/2940] Added template_global, fixes #657 --- CHANGES | 2 ++ flask/app.py | 32 ++++++++++++++++++++++++++++++++ flask/blueprints.py | 28 ++++++++++++++++++++++++++++ flask/testsuite/templating.py | 12 ++++++++++++ 4 files changed, 74 insertions(+) diff --git a/CHANGES b/CHANGES index 7f3374a1..eac9fad7 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Release date to be decided. :ref:`upgrading-to-010` for more information. - Added ``template_test`` methods in addition to the already existing ``template_filter`` method family. +- Added ``template_global`` methods in addition to the already existing + ``template_filter`` method family. - Set the content-length header for x-sendfile. - ``tojson`` filter now does not escape script blocks in HTML5 parsers. - Flask will now raise an error if you attempt to register a new function diff --git a/flask/app.py b/flask/app.py index f6d40be2..c9117727 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1165,6 +1165,38 @@ class Flask(_PackageBoundObject): self.jinja_env.tests[name or f.__name__] = f + @setupmethod + def template_global(self, name=None): + """A decorator that is used to register a custom template global function. + You can specify a name for the global function, otherwise the function + name will be used. Example:: + + @app.template_global() + def double(n): + return 2 * n + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + def decorator(f): + self.add_template_global(f, name=name) + return f + return decorator + + @setupmethod + def add_template_global(self, f, name=None): + """Register a custom template global function. Works exactly like the + :meth:`template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + self.jinja_env.globals[name or f.__name__] = f + @setupmethod def before_request(self, f): """Registers a function to run before each request.""" diff --git a/flask/blueprints.py b/flask/blueprints.py index 7ce23bbc..4575ec9b 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -237,6 +237,34 @@ class Blueprint(_PackageBoundObject): state.app.jinja_env.tests[name or f.__name__] = f self.record_once(register_template) + def app_template_global(self, name=None): + """Register a custom template global, available application wide. Like + :meth:`Flask.template_global` but for a blueprint. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + def decorator(f): + self.add_app_template_global(f, name=name) + return f + return decorator + + def add_app_template_global(self, f, name=None): + """Register a custom template global, available application wide. Like + :meth:`Flask.add_template_global` but for a blueprint. Works exactly + like the :meth:`app_template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + def register_template(state): + state.app.jinja_env.globals[name or f.__name__] = f + self.record_once(register_template) + def before_request(self, f): """Like :meth:`Flask.before_request` but for a blueprint. This function is only executed before each request that is handled by a function of diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 6345b710..635210f7 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -256,6 +256,18 @@ class TemplatingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_('Success!' in rv.data) + def test_add_template_global(self): + app = flask.Flask(__name__) + @app.template_global() + def get_stuff(): + return 42 + self.assert_('get_stuff' in app.jinja_env.globals.keys()) + self.assert_equal(app.jinja_env.globals['get_stuff'], get_stuff) + self.assert_(app.jinja_env.globals['get_stuff'](), 42) + with app.app_context(): + rv = flask.render_template_string('{{ get_stuff() }}') + self.assert_equal(rv, '42') + def test_custom_template_loader(self): class MyFlask(flask.Flask): def create_global_jinja_loader(self): From 6ab569b0e3791cf04db7c333bbf5b78d33255dad Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 27 Jan 2013 00:56:01 +0000 Subject: [PATCH 0290/2940] Added note on teardown in debug mode. Fixes #661 --- flask/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/app.py b/flask/app.py index c9117727..373479f5 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1250,6 +1250,13 @@ class Flask(_PackageBoundObject): When a teardown function was called because of a exception it will be passed an error object. + + .. admonition:: Debug Note + + In debug mode Flask will not tear down a request on an exception + immediately. Instead if will keep it alive so that the interactive + debugger can still access it. This behavior can be controlled + by the ``PRESERVE_CONTEXT_ON_EXCEPTION`` configuration variable. """ self.teardown_request_funcs.setdefault(None, []).append(f) return f From 6bd0080575a3413e349a20a658cc720809782238 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Mon, 28 Jan 2013 15:08:54 +0000 Subject: [PATCH 0291/2940] Added workaround for Chrome cookies --- CHANGES | 2 ++ flask/sessions.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index eac9fad7..33013bdd 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,8 @@ Release date to be decided. cause caching. - Flask will no longer invoke the wrong error handlers if a proxy exception is passed through. +- Added a workaround for chrome's cookies in localhost not working + as intended with domain names. Version 0.9 ----------- diff --git a/flask/sessions.py b/flask/sessions.py index bbf41ba0..ea2e999f 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -192,7 +192,13 @@ class SessionInterface(object): return app.config['SESSION_COOKIE_DOMAIN'] if app.config['SERVER_NAME'] is not None: # chop of the port which is usually not supported by browsers - return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] + rv = '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] + # Google chrome does not like cookies set to .localhost, so + # we just go with no domain then. Flask documents anyways that + # cross domain cookies need a fully qualified domain name + if rv == '.localhost': + rv = None + return rv def get_cookie_path(self, app): """Returns the path for which the cookie should be valid. The From bfeee756967ba1f6a74a6f2f4c3c617b4c0c4245 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Tue, 29 Jan 2013 19:31:45 +0000 Subject: [PATCH 0292/2940] Changed session cookie defaults to work better with google chrome --- CHANGES | 2 ++ flask/sessions.py | 10 ++++++++++ flask/testsuite/basic.py | 16 ++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/CHANGES b/CHANGES index 33013bdd..bbf51704 100644 --- a/CHANGES +++ b/CHANGES @@ -49,6 +49,8 @@ Release date to be decided. exception is passed through. - Added a workaround for chrome's cookies in localhost not working as intended with domain names. +- Changed logic for picking defaults for cookie values from sessions + to work better with Google Chrome. Version 0.9 ----------- diff --git a/flask/sessions.py b/flask/sessions.py index ea2e999f..4a156d36 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -193,11 +193,21 @@ class SessionInterface(object): if app.config['SERVER_NAME'] is not None: # chop of the port which is usually not supported by browsers rv = '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] + # Google chrome does not like cookies set to .localhost, so # we just go with no domain then. Flask documents anyways that # cross domain cookies need a fully qualified domain name if rv == '.localhost': rv = None + + # If we infer the cookie domain from the server name we need + # to check if we are in a subpath. In that case we can't + # set a cross domain cookie. + if rv is not None: + path = self.get_cookie_path(app) + if path != '/': + rv = rv.lstrip('.') + return rv def get_cookie_path(self, app): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index efee244a..aaf02fce 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -190,6 +190,22 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower()) self.assert_('httponly' in rv.headers['set-cookie'].lower()) + def test_session_using_server_name_port_and_path(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='example.com:8080', + APPLICATION_ROOT='/foo' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/foo') + self.assert_('domain=example.com' in rv.headers['set-cookie'].lower()) + self.assert_('path=/foo' in rv.headers['set-cookie'].lower()) + self.assert_('httponly' in rv.headers['set-cookie'].lower()) + def test_session_using_application_root(self): class PrefixPathMiddleware(object): def __init__(self, app, prefix): From a754d28302b64dc9d86a5454b15d174b0115fa21 Mon Sep 17 00:00:00 2001 From: lambdadi <lambdadi@gmail.com> Date: Mon, 11 Feb 2013 18:55:24 +0530 Subject: [PATCH 0293/2940] Database improperly closed in example code os.unlink(flaskr.DATABASE) causes the actual application database to be purged; whereas, I reckon, one wants the _test_ database to be removed. So every time I ran it, the test passed, but ended up crashing the live app for want of a valid database. I avoided using the sample code in examples/flaskr thus far, as I chose to type out code from the turorial docs. The actual example code looks good - at least to my beginner's eye. --- docs/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index d4d9259c..84a6c277 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -102,7 +102,7 @@ test method to our class, like this:: def tearDown(self): os.close(self.db_fd) - os.unlink(flaskr.DATABASE) + os.unlink(flaskr.app.config['DATABASE']) def test_empty_db(self): rv = self.app.get('/') From c999c179a25998bfd52db359b243a07fdb8584c1 Mon Sep 17 00:00:00 2001 From: Devon Mizelle <dev@devon.so> Date: Sun, 17 Feb 2013 11:04:20 -0500 Subject: [PATCH 0294/2940] fixes #677 - mistype in docs/quickstart --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e9d6b388..cf5befa3 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -695,7 +695,7 @@ Imagine you have a view like this: return render_template('error.html'), 404 You just need to wrap the return expression with -:func:`~flask.make_response` and get the result object to modify it, then +:func:`~flask.make_response` and get the response object to modify it, then return it: .. sourcecode:: python From a92588f161221dc1428eb3bc701ca79eb2a3ca11 Mon Sep 17 00:00:00 2001 From: Alex Couper <alex.couper@glassesdirect.com> Date: Fri, 22 Feb 2013 11:15:40 +0000 Subject: [PATCH 0295/2940] Add closing html tag --- docs/patterns/templateinheritance.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/patterns/templateinheritance.rst b/docs/patterns/templateinheritance.rst index 70015ecc..1292f26e 100644 --- a/docs/patterns/templateinheritance.rst +++ b/docs/patterns/templateinheritance.rst @@ -28,14 +28,15 @@ document that you might use for a simple two-column page. It's the job of <title>{% block title %}{% endblock %} - My Webpage {% endblock %} - -

{% block content %}{% endblock %}
- - + +
{% block content %}{% endblock %}
+ + + In this example, the ``{% block %}`` tags define four blocks that child templates can fill in. All the `block` tag does is tell the template engine that a From cc8a85d7539bf7f1a6a0affd55f338e46af0d1c1 Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 22 Feb 2013 11:21:17 +0000 Subject: [PATCH 0296/2940] Move docs explaining instantiating Flask --- docs/quickstart.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index e9d6b388..082209b4 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -38,15 +38,14 @@ should see your hello world greeting. So what did that code do? 1. First we imported the :class:`~flask.Flask` class. An instance of this - class will be our WSGI application. The first argument is the name of - the application's module. If you are using a single module (as in this - example), you should use `__name__` because depending on if it's started as - application or imported as module the name will be different (``'__main__'`` - versus the actual import name). For more information, have a look at the - :class:`~flask.Flask` documentation. -2. Next we create an instance of this class. We pass it the name of the module - or package. This is needed so that Flask knows where to look for templates, - static files, and so on. + class will be our WSGI application. +2. Next we create an instance of this class. The first argument is the name of + the application's module or package. If you are using a single module (as + in this example), you should use `__name__` because depending on if it's + started as application or imported as module the name will be different + (``'__main__'`` versus the actual import name). For more information, have + a look at the :class:`~flask.Flask` documentation. This is needed so that + Flask knows where to look for templates, static files, and so on. 3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask what URL should trigger our function. 4. The function is given a name which is also used to generate URLs for that From 8ee01ad5ed7296bb1030f3412c866b6fb39a92ad Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 22 Feb 2013 15:33:20 +0000 Subject: [PATCH 0297/2940] Put the link to Flask class docs at the end. --- docs/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 082209b4..01c6b833 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -43,9 +43,9 @@ So what did that code do? the application's module or package. If you are using a single module (as in this example), you should use `__name__` because depending on if it's started as application or imported as module the name will be different - (``'__main__'`` versus the actual import name). For more information, have - a look at the :class:`~flask.Flask` documentation. This is needed so that - Flask knows where to look for templates, static files, and so on. + (``'__main__'`` versus the actual import name). This is needed so that + Flask knows where to look for templates, static files, and so on. For more + information, have a look at the :class:`~flask.Flask` documentation. 3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask what URL should trigger our function. 4. The function is given a name which is also used to generate URLs for that From 1be13297c1518d01fb09bd65f7bfb6ca7449c424 Mon Sep 17 00:00:00 2001 From: Alex Couper Date: Fri, 22 Feb 2013 15:34:16 +0000 Subject: [PATCH 0298/2940] Remove comma. --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 01c6b833..421235dc 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -45,7 +45,7 @@ So what did that code do? started as application or imported as module the name will be different (``'__main__'`` versus the actual import name). This is needed so that Flask knows where to look for templates, static files, and so on. For more - information, have a look at the :class:`~flask.Flask` documentation. + information have a look at the :class:`~flask.Flask` documentation. 3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask what URL should trigger our function. 4. The function is given a name which is also used to generate URLs for that From b2aae044ca9c9875c594d57a3bd92bd80d8d56ff Mon Sep 17 00:00:00 2001 From: Paulo Poiati Date: Wed, 27 Feb 2013 23:46:17 -0300 Subject: [PATCH 0299/2940] Flash messages signals If we send a signal when a template is rendered why not when a message is flashed? One real world use case is in tests, this signal should make flash messages expectation easier to implement. --- flask/__init__.py | 2 +- flask/helpers.py | 3 +++ flask/signals.py | 1 + flask/testsuite/signals.py | 27 +++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 6e7883fb..52eec667 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -34,7 +34,7 @@ from .templating import render_template, render_template_string # the signals from .signals import signals_available, template_rendered, request_started, \ request_finished, got_request_exception, request_tearing_down, \ - appcontext_tearing_down + appcontext_tearing_down, message_flashed # We're not exposing the actual json module but a convenient wrapper around # it. diff --git a/flask/helpers.py b/flask/helpers.py index fe651004..d24dde6b 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -35,6 +35,7 @@ except ImportError: from jinja2 import FileSystemLoader +from .signals import message_flashed from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request @@ -361,6 +362,8 @@ def flash(message, category='message'): flashes = session.get('_flashes', []) flashes.append((category, message)) session['_flashes'] = flashes + message_flashed.send(current_app._get_current_object(), + message=message, category=category) def get_flashed_messages(with_categories=False, category_filter=[]): diff --git a/flask/signals.py b/flask/signals.py index 78a77bd5..14b728c6 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -50,3 +50,4 @@ request_finished = _signals.signal('request-finished') request_tearing_down = _signals.signal('request-tearing-down') got_request_exception = _signals.signal('got-request-exception') appcontext_tearing_down = _signals.signal('appcontext-tearing-down') +message_flashed = _signals.signal('message-flashed') diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index da1a68ca..0e5d0cea 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -8,6 +8,8 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement + import flask import unittest from flask.testsuite import FlaskTestCase @@ -95,6 +97,31 @@ class SignalsTestCase(FlaskTestCase): finally: flask.got_request_exception.disconnect(record, app) + def test_flash_signal(self): + app = flask.Flask(__name__) + app.config['SECRET_KEY'] = 'secret' + + @app.route('/') + def index(): + flask.flash('This is a flash message', category='notice') + return flask.redirect('/other') + + recorded = [] + def record(sender, message, category): + recorded.append((message, category)) + + flask.message_flashed.connect(record, app) + try: + client = app.test_client() + with client.session_transaction(): + client.get('/') + self.assert_equal(len(recorded), 1) + message, category = recorded[0] + self.assert_equal(message, 'This is a flash message') + self.assert_equal(category, 'notice') + finally: + flask.message_flashed.disconnect(record, app) + def suite(): suite = unittest.TestSuite() From cc5d6134c76d92134bc555706ca2d02af11e1d35 Mon Sep 17 00:00:00 2001 From: OrangeTux Date: Thu, 28 Feb 2013 11:12:16 +0100 Subject: [PATCH 0300/2940] Update css.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improved consistency: lower cased all hexadecimal descriptions of colors. --- docs/tutorial/css.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/css.rst b/docs/tutorial/css.rst index 03f62ed1..b86225c4 100644 --- a/docs/tutorial/css.rst +++ b/docs/tutorial/css.rst @@ -10,7 +10,7 @@ folder we created before: .. sourcecode:: css body { font-family: sans-serif; background: #eee; } - a, h1, h2 { color: #377BA8; } + a, h1, h2 { color: #377ba8; } h1, h2 { font-family: 'Georgia', serif; margin: 0; } h1 { border-bottom: 2px solid #eee; } h2 { font-size: 1.2em; } @@ -24,8 +24,8 @@ folder we created before: .add-entry dl { font-weight: bold; } .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; margin-bottom: 1em; background: #fafafa; } - .flash { background: #CEE5F5; padding: 0.5em; - border: 1px solid #AACBE2; } - .error { background: #F0D6D6; padding: 0.5em; } + .flash { background: #cee5F5; padding: 0.5em; + border: 1px solid #aacbe2; } + .error { background: #f0d6d6; padding: 0.5em; } Continue with :ref:`tutorial-testing`. From 8a6cba9e63567556468676b556fba43da6cba3f2 Mon Sep 17 00:00:00 2001 From: "Michael N. Gagnon" Date: Fri, 15 Mar 2013 07:31:00 -0700 Subject: [PATCH 0301/2940] fix typo in quickstart guide --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b30d1b64..f79a991f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -513,7 +513,7 @@ attributes mentioned above:: return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' - # the code below this is executed if the request method + # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error) From 9d8674d5b64db6ceab2bf331776a8900b8a9a636 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 16 Mar 2013 11:13:07 +0100 Subject: [PATCH 0302/2940] Added more information about app factories. --- docs/patterns/appfactories.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index 2a6190ea..3ef80b42 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -30,6 +30,9 @@ The idea is to set up the application in a function. Like this:: app = Flask(__name__) app.config.from_pyfile(config_filename) + from yourapplication.model import db + db.init_app(app) + from yourapplication.views.admin import admin from yourapplication.views.frontend import frontend app.register_blueprint(admin) @@ -51,6 +54,21 @@ get access to the application with the config? Use Here we look up the name of a template in the config. +Extension objects are not initially bound to an application. Using +``db.init_app``, the app gets configured for the extension. No +application-specific state is stored on the extension object, so one extension +object can be used for multiple apps. For more information about the design of +extensions refer to :doc:`/extensiondev`. + +Your `model.py` might look like this when using `Flask-SQLAlchemy +`_:: + + from flask.ext.sqlalchemy import SQLAlchemy + # no app object passed! Instead we use use db.init_app in the factory. + db = SQLAlchemy() + + # create some models + Using Applications ------------------ From 4366bb392a5d41373d0a87c98a75eed59ceffd60 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Mar 2013 21:04:06 +0000 Subject: [PATCH 0303/2940] Documented new signal message_flashed --- CHANGES | 1 + docs/api.rst | 8 ++++++++ docs/signals.rst | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGES b/CHANGES index bbf51704..ea7be9a7 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,7 @@ Release date to be decided. as intended with domain names. - Changed logic for picking defaults for cookie values from sessions to work better with Google Chrome. +- Added `message_flashed` signal that simplifies flashing testing. Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index 12e4b966..6e08241e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -524,6 +524,14 @@ Signals An `exc` keyword argument is passed with the exception that caused the teardown. +.. data:: message_flashed + + This signal is sent when the application is flashing a message. The + messages is sent as `message` keyword argument and the category as + `category`. + + .. versionadded:: 0.10 + .. currentmodule:: None .. class:: flask.signals.Namespace diff --git a/docs/signals.rst b/docs/signals.rst index 5f0af048..4d96cc14 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -291,4 +291,22 @@ The following signals exist in Flask: This will also be passed an `exc` keyword argument that has a reference to the exception that caused the teardown if there was one. +.. data:: flask.message_flashed + :noindex: + + This signal is sent when the application is flashing a message. The + messages is sent as `message` keyword argument and the category as + `category`. + + Example subscriber:: + + recorded = [] + def record(sender, message, category, **extra): + recorded.append((message, category)) + + from flask import message_flashed + message_flashed.connect(record, app) + + .. versionadded:: 0.10 + .. _blinker: http://pypi.python.org/pypi/blinker From 78ae0ec7f8d436df789d98c64e3579a196f52ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kalkosi=C5=84ski?= Date: Mon, 25 Mar 2013 12:29:28 +0100 Subject: [PATCH 0304/2940] Add import to jsonify example. --- flask/json.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/json.py b/flask/json.py index a85bdc63..f25b11b1 100644 --- a/flask/json.py +++ b/flask/json.py @@ -141,6 +141,8 @@ def jsonify(*args, **kwargs): to this function are the same as to the :class:`dict` constructor. Example usage:: + + from flask import jsonify @app.route('/_get_current_user') def get_current_user(): From 1723990aee6ef4587d851081c36b824418549266 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 26 Mar 2013 21:23:55 +0000 Subject: [PATCH 0305/2940] Fixed a few typos on quickstart --- docs/quickstart.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index f79a991f..b455e070 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -54,7 +54,7 @@ So what did that code do? 5. Finally we use the :meth:`~flask.Flask.run` function to run the local server with our application. The ``if __name__ == '__main__':`` makes sure the server only runs if the script is executed directly from the Python - interpreter and not used as imported module. + interpreter and not used as an imported module. To stop the server, hit control-C. @@ -143,8 +143,8 @@ Variable Rules `````````````` To add variable parts to a URL you can mark these special sections as -````. Such a part is then passed as keyword argument to your -function. Optionally a converter can be specified by specifying a rule with +````. Such a part is then passed as a keyword argument to your +function. Optionally a converter can be used by specifying a rule with ````. Here are some nice examples:: @app.route('/user/') @@ -191,10 +191,10 @@ The following converters exist: rather like the pathname of a file on UNIX-like systems. Accessing the URL with a trailing slash will produce a 404 "Not Found" error. - This behavior allows relative URLs to continue working if users access the - page when they forget a trailing slash, consistent with how Apache - and other servers work. Also, the URLs will stay unique, which helps search - engines avoid indexing the same page twice. + This behavior allows relative URLs to continue working even if the trailing + slash is ommited, consistent with how Apache and other servers work. Also, + the URLs will stay unique, which helps search engines avoid indexing the + same page twice. .. _url-building: From 0a5d62f9ee44ccf6fed1584ef68b0c4c7d728b11 Mon Sep 17 00:00:00 2001 From: Ed Burnett Date: Tue, 26 Mar 2013 16:56:05 -0700 Subject: [PATCH 0306/2940] Updated jquery.rst with the current Google Developer hosted libraries URL and jquery version --- docs/patterns/jquery.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/jquery.rst b/docs/patterns/jquery.rst index 72e401fc..1bd49533 100644 --- a/docs/patterns/jquery.rst +++ b/docs/patterns/jquery.rst @@ -31,11 +31,11 @@ to add a script statement to the bottom of your `` to load jQuery: url_for('static', filename='jquery.js') }}"> Another method is using Google's `AJAX Libraries API -`_ to load jQuery: +`_ to load jQuery: .. sourcecode:: html - + From 02150c0f2bf62fb980467e53f4f608d11f332f07 Mon Sep 17 00:00:00 2001 From: Ed Burnett Date: Tue, 26 Mar 2013 16:57:16 -0700 Subject: [PATCH 0307/2940] Added self to AUTHORS file --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 0f2a9827..22a4b3e8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,3 +28,4 @@ Patches and Suggestions - Stephane Wirtel - Thomas Schranz - Zhao Xiaohong +- Edmond Burnett From b9355a7d5f79cfa06324a72cbaef4b9ec7a299d3 Mon Sep 17 00:00:00 2001 From: Steve Leonard Date: Thu, 4 Apr 2013 06:56:24 -0300 Subject: [PATCH 0308/2940] Mention register_error_handler in errorhandler doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation for @errorhandler gives "app.error_handler_spec[None][404] = page_not_found" as an example for adding an error handler without the decorator.  However, register_error_handler appears to be the correct way to do this (added 0.7), and it eliminates the problems with modifying error_handler_spec directly. --- flask/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flask/app.py b/flask/app.py index 373479f5..036903a3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1065,6 +1065,11 @@ class Flask(_PackageBoundObject): The first `None` refers to the active blueprint. If the error handler should be application wide `None` shall be used. + .. versionadded:: 0.7 + Use :meth:`register_error_handler` instead of modifying + :attr:`error_handler_spec` directly, for application wide error + handlers. + .. versionadded:: 0.7 One can now additionally also register custom exception types that do not necessarily have to be a subclass of the From f74f446961f9fd8ecb2edffca53b82016cc41343 Mon Sep 17 00:00:00 2001 From: Trey Long Date: Thu, 4 Apr 2013 12:31:42 -0300 Subject: [PATCH 0309/2940] fixing process_response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flask.process_response will throw away functions is receives from ctx._after_request_functions if there is a Blueprint that has used @after_request. --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 373479f5..2da379f3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1679,7 +1679,7 @@ class Flask(_PackageBoundObject): bp = ctx.request.blueprint funcs = ctx._after_request_functions if bp is not None and bp in self.after_request_funcs: - funcs = reversed(self.after_request_funcs[bp]) + funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: From 8ca7fc47d0c9036f12ca628aabccb44a8dc3858d Mon Sep 17 00:00:00 2001 From: Alexander Thaller Date: Fri, 5 Apr 2013 13:48:08 +0300 Subject: [PATCH 0310/2940] Update README No need to manually initialize the database with a call to `init_db()` as this call is done before `app.run()` in flaskr.py, when you run the file. --- examples/flaskr/README | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/flaskr/README b/examples/flaskr/README index 9ab20589..9b5b9215 100644 --- a/examples/flaskr/README +++ b/examples/flaskr/README @@ -1,4 +1,3 @@ - / Flaskr / a minimal blog application @@ -14,11 +13,7 @@ export an FLASKR_SETTINGS environment variable pointing to a configuration file. - 2. fire up a python shell and run this: - - >>> from flaskr import init_db; init_db() - - 3. now you can run the flaskr.py file with your + 2. now you can run the flaskr.py file with your python interpreter and the application will greet you on http://localhost:5000/ From 0faed95385d731babac7d46a28e90ee25e702abd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 21 Mar 2013 21:04:06 +0000 Subject: [PATCH 0311/2940] Documented new signal message_flashed --- CHANGES | 1 + docs/api.rst | 8 ++++++++ docs/signals.rst | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGES b/CHANGES index bbf51704..ea7be9a7 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,7 @@ Release date to be decided. as intended with domain names. - Changed logic for picking defaults for cookie values from sessions to work better with Google Chrome. +- Added `message_flashed` signal that simplifies flashing testing. Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index 12e4b966..6e08241e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -524,6 +524,14 @@ Signals An `exc` keyword argument is passed with the exception that caused the teardown. +.. data:: message_flashed + + This signal is sent when the application is flashing a message. The + messages is sent as `message` keyword argument and the category as + `category`. + + .. versionadded:: 0.10 + .. currentmodule:: None .. class:: flask.signals.Namespace diff --git a/docs/signals.rst b/docs/signals.rst index 5f0af048..4d96cc14 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -291,4 +291,22 @@ The following signals exist in Flask: This will also be passed an `exc` keyword argument that has a reference to the exception that caused the teardown if there was one. +.. data:: flask.message_flashed + :noindex: + + This signal is sent when the application is flashing a message. The + messages is sent as `message` keyword argument and the category as + `category`. + + Example subscriber:: + + recorded = [] + def record(sender, message, category, **extra): + recorded.append((message, category)) + + from flask import message_flashed + message_flashed.connect(record, app) + + .. versionadded:: 0.10 + .. _blinker: http://pypi.python.org/pypi/blinker From d1bf82b0f4b180ac7523a7b7e63e7246039d4e71 Mon Sep 17 00:00:00 2001 From: Jason Moon Date: Sun, 14 Apr 2013 00:10:38 -0700 Subject: [PATCH 0312/2940] Fixed typo in URL Processor documentation --- docs/patterns/urlprocessors.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/urlprocessors.rst b/docs/patterns/urlprocessors.rst index 778a5a6b..3f65d758 100644 --- a/docs/patterns/urlprocessors.rst +++ b/docs/patterns/urlprocessors.rst @@ -65,7 +65,7 @@ dictionary and put it somewhere else:: def pull_lang_code(endpoint, values): g.lang_code = values.pop('lang_code', None) -That way you no longer have to do the `lang_code` assigment to +That way you no longer have to do the `lang_code` assignment to :data:`~flask.g` in every function. You can further improve that by writing your own decorator that prefixes URLs with the language code, but the more beautiful solution is using a blueprint. Once the From 1358fd9f3e0d1cdfa2a948a1176b276188ea959e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cenk=20Alt=C4=B1?= Date: Wed, 24 Apr 2013 09:42:52 +0300 Subject: [PATCH 0313/2940] Syntax highlighting for PyPI long description --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4a9182b9..2f9c95ca 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ intentions. And before you ask: It's BSD licensed! Flask is Fun ```````````` -:: +.. code:: python from flask import Flask app = Flask(__name__) @@ -23,7 +23,7 @@ Flask is Fun And Easy to Setup ````````````````` -:: +.. code:: bash $ pip install Flask $ python hello.py From a9b22af9bc77fb89047faa45090dc39631c2d0d5 Mon Sep 17 00:00:00 2001 From: Akshar Raaj Date: Fri, 3 May 2013 17:41:43 +0530 Subject: [PATCH 0314/2940] Fixed documentation in 'Design Decisions in Flask' --- docs/design.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design.rst b/docs/design.rst index ee83840e..f0f7126d 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -82,7 +82,7 @@ things (:ref:`app-factories`). The Routing System ------------------ -Flask uses the Werkzeug routing system which has was designed to +Flask uses the Werkzeug routing system which was designed to automatically order routes by complexity. This means that you can declare routes in arbitrary order and they will still work as expected. This is a requirement if you want to properly implement decorator based routing From 097353695e3178a38403b204ae4889c8a32bf997 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 11:00:04 +0100 Subject: [PATCH 0315/2940] Added flask.copy_current_request_context which simplies working with greenlets --- CHANGES | 2 + docs/api.rst | 2 + flask/__init__.py | 2 +- flask/app.py | 6 ++ flask/ctx.py | 77 +++++++++++++++- flask/testsuite/basic.py | 110 +--------------------- flask/testsuite/reqctx.py | 187 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 272 insertions(+), 114 deletions(-) create mode 100644 flask/testsuite/reqctx.py diff --git a/CHANGES b/CHANGES index ea7be9a7..e383b56f 100644 --- a/CHANGES +++ b/CHANGES @@ -52,6 +52,8 @@ Release date to be decided. - Changed logic for picking defaults for cookie values from sessions to work better with Google Chrome. - Added `message_flashed` signal that simplifies flashing testing. +- Added support for copying of request contexts for better working with + greenlets. Version 0.9 ----------- diff --git a/docs/api.rst b/docs/api.rst index 6e08241e..291bfabb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -280,6 +280,8 @@ Useful Functions and Classes .. autofunction:: has_request_context +.. autofunction:: copy_current_request_context + .. autofunction:: has_app_context .. autofunction:: url_for diff --git a/flask/__init__.py b/flask/__init__.py index 52eec667..978a4a4c 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -26,7 +26,7 @@ from .helpers import url_for, flash, send_file, send_from_directory, \ from .globals import current_app, g, request, session, _request_ctx_stack, \ _app_ctx_stack from .ctx import has_request_context, has_app_context, \ - after_this_request + after_this_request, copy_current_request_context from .module import Module from .blueprints import Blueprint from .templating import render_template, render_template_string diff --git a/flask/app.py b/flask/app.py index 373479f5..fba200e8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1821,3 +1821,9 @@ class Flask(_PackageBoundObject): def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`.""" return self.wsgi_app(environ, start_response) + + def __repr__(self): + return '<%s %r>' % ( + self.__class__.__name__, + self.name, + ) diff --git a/flask/ctx.py b/flask/ctx.py index a1d9af62..6b271687 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -10,6 +10,7 @@ """ import sys +from functools import update_wrapper from werkzeug.exceptions import HTTPException @@ -19,7 +20,24 @@ from .module import blueprint_is_module class _AppCtxGlobals(object): """A plain object.""" - pass + + def __getitem__(self, name): + try: + return getattr(self, name) + except AttributeError: + return None + + def __setitem__(self, name, value): + setattr(self, name, value) + + def __delitem__(self, name, value): + delattr(self, name, value) + + def __repr__(self): + top = _app_ctx_stack.top + if top is not None: + return '' % top.app.name + return object.__repr__(self) def after_this_request(f): @@ -47,6 +65,41 @@ def after_this_request(f): return f +def copy_current_request_context(f): + """A helper function that decorates a function to retain the current + request context. This is useful when working with greenlets. The moment + the function is decorated a copy of the request context is created and + then pushed when the function is called. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request like you + # would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + top = _request_ctx_stack.top + if top is None: + raise RuntimeError('This decorator can only be used at local scopes ' + 'when a request context is on the stack. For instance within ' + 'view functions.') + reqctx = top.copy() + def wrapper(*args, **kwargs): + with reqctx: + return f(*args, **kwargs) + return update_wrapper(wrapper, f) + + def has_request_context(): """If you have code that wants to test if a request context is there or not this function can be used. For instance, you may want to take advantage @@ -161,9 +214,11 @@ class RequestContext(object): that situation, otherwise your unittests will leak memory. """ - def __init__(self, app, environ): + def __init__(self, app, environ, request=None): self.app = app - self.request = app.request_class(environ) + if request is None: + request = app.request_class(environ) + self.request = request self.url_adapter = app.create_url_adapter(self.request) self.flashes = None self.session = None @@ -202,6 +257,20 @@ class RequestContext(object): g = property(_get_g, _set_g) del _get_g, _set_g + def copy(self): + """Creates a copy of this request context with the same request object. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + """ + return self.__class__(self.app, + environ=self.request.environ, + request=self.request + ) + def match_request(self): """Can be overridden by a subclass to hook into the matching of the request. @@ -299,5 +368,5 @@ class RequestContext(object): self.__class__.__name__, self.request.url, self.request.method, - self.app.name + self.app.name, ) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index aaf02fce..445b6b41 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -666,19 +666,6 @@ class BasicFunctionalityTestCase(FlaskTestCase): else: self.fail('Expected exception') - def test_teardown_on_pop(self): - buffer = [] - app = flask.Flask(__name__) - @app.teardown_request - def end_of_request(exception): - buffer.append(exception) - - ctx = app.test_request_context() - ctx.push() - self.assert_equal(buffer, []) - ctx.pop() - self.assert_equal(buffer, [None]) - def test_response_creation(self): app = flask.Flask(__name__) @app.route('/unicode') @@ -821,53 +808,6 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(repr(flask.g), '') self.assertFalse(flask.g) - def test_proper_test_request_context(self): - app = flask.Flask(__name__) - app.config.update( - SERVER_NAME='localhost.localdomain:5000' - ) - - @app.route('/') - def index(): - return None - - @app.route('/', subdomain='foo') - def sub(): - return None - - with app.test_request_context('/'): - self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/') - - with app.test_request_context('/'): - self.assert_equal(flask.url_for('sub', _external=True), 'http://foo.localhost.localdomain:5000/') - - try: - with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): - pass - except Exception, e: - self.assert_(isinstance(e, ValueError)) - self.assert_equal(str(e), "the server name provided " + - "('localhost.localdomain:5000') does not match the " + \ - "server name from the WSGI environment ('localhost')") - - try: - app.config.update(SERVER_NAME='localhost') - with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}): - pass - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - app.config.update(SERVER_NAME='localhost:80') - with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): - pass - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - def test_test_app_proper_environ(self): app = flask.Flask(__name__) app.config.update( @@ -1012,7 +952,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): values = dict() app.inject_url_defaults('foo.bar.baz.view', values) expected = dict(page='login') - self.assert_equal(values, expected) + self.assert_equal(values, expected) with app.test_request_context('/somepage'): url = flask.url_for('foo.bar.baz.view') @@ -1127,53 +1067,6 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_(flask._app_ctx_stack.top is None) -class ContextTestCase(FlaskTestCase): - - def test_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return 'Hello %s!' % flask.request.args['name'] - @app.route('/meh') - def meh(): - return flask.request.url - - with app.test_request_context('/?name=World'): - self.assert_equal(index(), 'Hello World!') - with app.test_request_context('/meh'): - self.assert_equal(meh(), 'http://localhost/meh') - self.assert_(flask._request_ctx_stack.top is None) - - def test_context_test(self): - app = flask.Flask(__name__) - self.assert_(not flask.request) - self.assert_(not flask.has_request_context()) - ctx = app.test_request_context() - ctx.push() - try: - self.assert_(flask.request) - self.assert_(flask.has_request_context()) - finally: - ctx.pop() - - def test_manual_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return 'Hello %s!' % flask.request.args['name'] - - ctx = app.test_request_context('/?name=World') - ctx.push() - self.assert_equal(index(), 'Hello World!') - ctx.pop() - try: - index() - except RuntimeError: - pass - else: - self.assert_(0, 'expected runtime error') - - class SubdomainTestCase(FlaskTestCase): def test_basic_support(self): @@ -1251,6 +1144,5 @@ class SubdomainTestCase(FlaskTestCase): def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase)) - suite.addTest(unittest.makeSuite(ContextTestCase)) suite.addTest(unittest.makeSuite(SubdomainTestCase)) return suite diff --git a/flask/testsuite/reqctx.py b/flask/testsuite/reqctx.py new file mode 100644 index 00000000..a93523e7 --- /dev/null +++ b/flask/testsuite/reqctx.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.reqctx + ~~~~~~~~~~~~~~~~~~~~~~ + + Tests the request context. + + :copyright: (c) 2012 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from __future__ import with_statement + +import flask +import unittest +try: + from greenlet import greenlet +except ImportError: + greenlet = None +from flask.testsuite import FlaskTestCase + + +class RequestContextTestCase(FlaskTestCase): + + def test_teardown_on_pop(self): + buffer = [] + app = flask.Flask(__name__) + @app.teardown_request + def end_of_request(exception): + buffer.append(exception) + + ctx = app.test_request_context() + ctx.push() + self.assert_equal(buffer, []) + ctx.pop() + self.assert_equal(buffer, [None]) + + def test_proper_test_request_context(self): + app = flask.Flask(__name__) + app.config.update( + SERVER_NAME='localhost.localdomain:5000' + ) + + @app.route('/') + def index(): + return None + + @app.route('/', subdomain='foo') + def sub(): + return None + + with app.test_request_context('/'): + self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/') + + with app.test_request_context('/'): + self.assert_equal(flask.url_for('sub', _external=True), 'http://foo.localhost.localdomain:5000/') + + try: + with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): + pass + except Exception, e: + self.assert_(isinstance(e, ValueError)) + self.assert_equal(str(e), "the server name provided " + + "('localhost.localdomain:5000') does not match the " + \ + "server name from the WSGI environment ('localhost')") + + try: + app.config.update(SERVER_NAME='localhost') + with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}): + pass + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost:80') + with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): + pass + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + def test_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return 'Hello %s!' % flask.request.args['name'] + @app.route('/meh') + def meh(): + return flask.request.url + + with app.test_request_context('/?name=World'): + self.assert_equal(index(), 'Hello World!') + with app.test_request_context('/meh'): + self.assert_equal(meh(), 'http://localhost/meh') + self.assert_(flask._request_ctx_stack.top is None) + + def test_context_test(self): + app = flask.Flask(__name__) + self.assert_(not flask.request) + self.assert_(not flask.has_request_context()) + ctx = app.test_request_context() + ctx.push() + try: + self.assert_(flask.request) + self.assert_(flask.has_request_context()) + finally: + ctx.pop() + + def test_manual_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return 'Hello %s!' % flask.request.args['name'] + + ctx = app.test_request_context('/?name=World') + ctx.push() + self.assert_equal(index(), 'Hello World!') + ctx.pop() + try: + index() + except RuntimeError: + pass + else: + self.assert_(0, 'expected runtime error') + + def test_greenlet_context_copying(self): + app = flask.Flask(__name__) + greenlets = [] + + @app.route('/') + def index(): + reqctx = flask._request_ctx_stack.top.copy() + def g(): + self.assert_(not flask.request) + self.assert_(not flask.current_app) + with reqctx: + self.assert_(flask.request) + self.assert_equal(flask.current_app, app) + self.assert_equal(flask.request.path, '/') + self.assert_equal(flask.request.args['foo'], 'bar') + self.assert_(not flask.request) + return 42 + greenlets.append(greenlet(g)) + return 'Hello World!' + + rv = app.test_client().get('/?foo=bar') + self.assert_equal(rv.data, 'Hello World!') + + result = greenlets[0].run() + self.assert_equal(result, 42) + + def test_greenlet_context_copying_api(self): + app = flask.Flask(__name__) + greenlets = [] + + @app.route('/') + def index(): + reqctx = flask._request_ctx_stack.top.copy() + @flask.copy_current_request_context + def g(): + self.assert_(flask.request) + self.assert_equal(flask.current_app, app) + self.assert_equal(flask.request.path, '/') + self.assert_equal(flask.request.args['foo'], 'bar') + return 42 + greenlets.append(greenlet(g)) + return 'Hello World!' + + rv = app.test_client().get('/?foo=bar') + self.assert_equal(rv.data, 'Hello World!') + + result = greenlets[0].run() + self.assert_equal(result, 42) + + # Disable test if we don't have greenlets available + if greenlet is None: + test_greenlet_context_copying = None + test_greenlet_context_copying_api = None + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(RequestContextTestCase)) + return suite From 833198c91be327d23210115b15b5f6ce26bda91d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 11:23:31 +0100 Subject: [PATCH 0316/2940] Added a missing comma --- docs/templating.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templating.rst b/docs/templating.rst index 8a12cd6f..b6e1fc0a 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -188,7 +188,7 @@ you have a Python list in context called `mylist`:: Context Processors ------------------ -To inject new variables automatically into the context of a template +To inject new variables automatically into the context of a template, context processors exist in Flask. Context processors run before the template is rendered and have the ability to inject new values into the template context. A context processor is a function that returns a From 2ba37d2b85ac2c6cacb62055162e2e7b5e9cc55e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 11:33:13 +0100 Subject: [PATCH 0317/2940] Fixed some rst markup problems --- flask/app.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flask/app.py b/flask/app.py index fba200e8..831066fa 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1171,14 +1171,14 @@ class Flask(_PackageBoundObject): You can specify a name for the global function, otherwise the function name will be used. Example:: - @app.template_global() - def double(n): - return 2 * n + @app.template_global() + def double(n): + return 2 * n .. versionadded:: 0.10 :param name: the optional name of the global function, otherwise the - function name will be used. + function name will be used. """ def decorator(f): self.add_template_global(f, name=name) @@ -1193,7 +1193,7 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.10 :param name: the optional name of the global function, otherwise the - function name will be used. + function name will be used. """ self.jinja_env.globals[name or f.__name__] = f From 18673ba370e6744049aa2622b5973506d80659e0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 11:33:36 +0100 Subject: [PATCH 0318/2940] Added uuid support for new session serialization and documented it --- docs/api.rst | 12 ++++++++++++ flask/sessions.py | 5 +++++ flask/testsuite/basic.py | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 291bfabb..27333079 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -224,6 +224,18 @@ implementation that Flask is using. .. autoclass:: SessionMixin :members: +.. autodata:: session_json_serializer + + This object provides dumping and loading methods similar to simplejson + but it also tags certain builtin Python objects that commonly appear in + sessions. Currently the following extended values are supported in + the JSON it dumps: + + - :class:`~markupsafe.Markup` objects + - :class:`~uuid.UUID` objects + - :class:`~datetime.datetime` objects + - :class:`tuple`\s + .. admonition:: Notice The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer diff --git a/flask/sessions.py b/flask/sessions.py index 4a156d36..31b5900b 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -9,6 +9,7 @@ :license: BSD, see LICENSE for more details. """ +import uuid import hashlib from datetime import datetime from werkzeug.http import http_date, parse_date @@ -58,6 +59,8 @@ class TaggedJSONSerializer(object): def _tag(value): if isinstance(value, tuple): return {' t': [_tag(x) for x in value]} + elif isinstance(value, uuid.UUID): + return {' u': value.hex} elif callable(getattr(value, '__html__', None)): return {' m': unicode(value.__html__())} elif isinstance(value, list): @@ -84,6 +87,8 @@ class TaggedJSONSerializer(object): the_key, the_value = obj.iteritems().next() if the_key == ' t': return tuple(the_value) + elif the_key == ' u': + return uuid.UUID(the_value) elif the_key == ' m': return Markup(the_value) elif the_key == ' d': diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 445b6b41..5f6dbe4b 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -12,6 +12,7 @@ from __future__ import with_statement import re +import uuid import flask import pickle import unittest @@ -319,10 +320,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.secret_key = 'development-key' app.testing = True now = datetime.utcnow().replace(microsecond=0) + the_uuid = uuid.uuid4() @app.after_request def modify_session(response): flask.session['m'] = flask.Markup('Hello!') + flask.session['u'] = the_uuid flask.session['dt'] = now flask.session['t'] = (1, 2, 3) return response @@ -337,6 +340,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(rv['m'], flask.Markup('Hello!')) self.assert_equal(type(rv['m']), flask.Markup) self.assert_equal(rv['dt'], now) + self.assert_equal(rv['u'], the_uuid) self.assert_equal(rv['t'], (1, 2, 3)) def test_flashes(self): From c349c91affd1d2123f50ab7a3d8c571da489338e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 11:35:45 +0100 Subject: [PATCH 0319/2940] Added support for UUID objects to JSON serializer as well --- flask/json.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/flask/json.py b/flask/json.py index f25b11b1..717eb2ab 100644 --- a/flask/json.py +++ b/flask/json.py @@ -8,6 +8,7 @@ :copyright: (c) 2012 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import uuid from datetime import datetime from .globals import current_app, request @@ -30,9 +31,9 @@ __all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump', class JSONEncoder(_json.JSONEncoder): """The default Flask JSON encoder. This one extends the default simplejson - encoder by also supporting ``datetime`` objects as well as ``Markup`` - objects which are serialized as RFC 822 datetime strings (same as the HTTP - date format). In order to support more data types override the + encoder by also supporting ``datetime`` objects, ``UUID`` as well as + ``Markup`` objects which are serialized as RFC 822 datetime strings (same + as the HTTP date format). In order to support more data types override the :meth:`default` method. """ @@ -55,6 +56,8 @@ class JSONEncoder(_json.JSONEncoder): """ if isinstance(o, datetime): return http_date(o) + if isinstance(o, uuid.UUID): + return str(o) if hasattr(o, '__html__'): return unicode(o.__html__()) return _json.JSONEncoder.default(self, o) From 574a97cd701df65ef1b4aff3f3e0549d69ce5419 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 11:51:38 +0100 Subject: [PATCH 0320/2940] Disabled memory tests by default --- Makefile | 5 ++++- flask/testsuite/regression.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 773c680e..4b5e4fe2 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ -.PHONY: clean-pyc ext-test test upload-docs docs audit +.PHONY: clean-pyc ext-test test test-with-mem upload-docs docs audit all: clean-pyc test test: python run-tests.py +test-with-mem: + RUN_FLASK_MEMORY_TESTS=1 python run-tests.py + audit: python setup.py audit diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py index 34fdefc0..00219856 100644 --- a/flask/testsuite/regression.py +++ b/flask/testsuite/regression.py @@ -11,12 +11,12 @@ from __future__ import with_statement +import os import gc import sys import flask import threading import unittest -from werkzeug.test import run_wsgi_app, create_environ from werkzeug.exceptions import NotFound from flask.testsuite import FlaskTestCase @@ -112,6 +112,7 @@ class ExceptionTestCase(FlaskTestCase): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(MemoryTestCase)) + if os.environ.get('RUN_FLASK_MEMORY_TESTS') == '1': + suite.addTest(unittest.makeSuite(MemoryTestCase)) suite.addTest(unittest.makeSuite(ExceptionTestCase)) return suite From b89af66346538764bf506571ac8a7bfaf5d31083 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 14:05:34 +0100 Subject: [PATCH 0321/2940] Revert "update README to markdown" This reverts commit fb6dee3639b5fad1800472cb5258d2603408e732. --- README | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 56 ------------------------------------------------------- 2 files changed, 55 insertions(+), 56 deletions(-) create mode 100644 README delete mode 100644 README.md diff --git a/README b/README new file mode 100644 index 00000000..7297f5db --- /dev/null +++ b/README @@ -0,0 +1,55 @@ + // Flask // + + web development, one drop at a time + + + ~ What is Flask? + + Flask is a microframework for Python based on Werkzeug + and Jinja2. It's intended for getting started very quickly + and was developed with best intentions in mind. + + ~ Is it ready? + + It's still not 1.0 but it's shaping up nicely and is + already widely used. Consider the API to slightly + improve over time but we don't plan on breaking it. + + ~ What do I need? + + Jinja 2.4 and Werkzeug 0.7 or later. + `pip` or `easy_install` will install them for you if you do + `pip install Flask`. I encourage you to use a virtualenv. + Check the docs for complete installation and usage + instructions. + + ~ Where are the docs? + + Go to http://flask.pocoo.org/docs/ for a prebuilt version + of the current documentation. Otherwise build them yourself + from the sphinx sources in the docs folder. + + ~ Where are the tests? + + Good that you're asking. The tests are in the + flask/testsuite package. To run the tests use the + `run-tests.py` file: + + $ python run-tests.py + + If it's not enough output for you, you can use the + `--verbose` flag: + + $ python run-tests.py --verbose + + If you just want one particular testcase to run you can + provide it on the command line: + + $ python run-tests.py test_to_run + + ~ Where can I get help? + + Either use the #pocoo IRC channel on irc.freenode.net or + ask on the mailinglist: http://flask.pocoo.org/mailinglist/ + + See http://flask.pocoo.org/community/ for more resources. diff --git a/README.md b/README.md deleted file mode 100644 index 90084361..00000000 --- a/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Flask - -Web development, one drop at a time - - -## What is Flask? - -Flask is a microframework for Python based on Werkzeug -and Jinja2. It's intended for getting started very quickly -and was developed with best intentions in mind. - -## Is it ready? - -It's still not 1.0 but it's shaping up nicely and is -already widely used. Consider the API to slightly -improve over time but we don't plan on breaking it. - -## What do I need? - -Jinja 2.4 and Werkzeug 0.7 or later. -`pip` or `easy_install` will install them for you if you do -`pip install Flask`. I encourage you to use a virtualenv. -Check the docs for complete installation and usage -instructions. - -## Where are the docs? - -Go to http://flask.pocoo.org/docs/ for a prebuilt version -of the current documentation. Otherwise build them yourself -from the sphinx sources in the docs folder. - -## Where are the tests? - -Good that you're asking. The tests are in the -flask/testsuite package. To run the tests use the -`run-tests.py` file: - - $ python run-tests.py - -If it's not enough output for you, you can use the -`--verbose` flag: - - $ python run-tests.py --verbose - -If you just want one particular testcase to run you can -provide it on the command line: - - $ python run-tests.py test_to_run - -## Where can I get help? - -Either use the #pocoo IRC channel on irc.freenode.net or -ask on the mailinglist: http://flask.pocoo.org/mailinglist/ - -See http://flask.pocoo.org/community/ for more resources. - From 521398d5e79b4a2b910815a2870d39d8182a6cc2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 May 2013 14:06:11 +0100 Subject: [PATCH 0322/2940] Added missing newlines to readme --- README | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README b/README index 7297f5db..9f7bed26 100644 --- a/README +++ b/README @@ -1,3 +1,5 @@ + + // Flask // web development, one drop at a time @@ -53,3 +55,5 @@ ask on the mailinglist: http://flask.pocoo.org/mailinglist/ See http://flask.pocoo.org/community/ for more resources. + + From 6caaa8a527134dc9aff6d5c442969e96f9c00f21 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 16:24:40 +0200 Subject: [PATCH 0323/2940] automated change using python-modernize: use 'as' in except --- flask/app.py | 10 +++++----- flask/config.py | 2 +- flask/ctx.py | 2 +- flask/debughelpers.py | 2 +- flask/helpers.py | 2 +- flask/testsuite/__init__.py | 2 +- flask/testsuite/basic.py | 20 ++++++++++---------- flask/testsuite/blueprints.py | 4 ++-- flask/testsuite/config.py | 8 ++++---- flask/testsuite/reqctx.py | 6 +++--- flask/testsuite/testing.py | 4 ++-- flask/wrappers.py | 2 +- 12 files changed, 32 insertions(+), 32 deletions(-) diff --git a/flask/app.py b/flask/app.py index cc9a2f90..3204e13f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1478,7 +1478,7 @@ class Flask(_PackageBoundObject): rv = self.preprocess_request() if rv is None: rv = self.dispatch_request() - except Exception, e: + except Exception as e: rv = self.handle_user_exception(e) response = self.make_response(rv) response = self.process_response(response) @@ -1516,9 +1516,9 @@ class Flask(_PackageBoundObject): methods = [] try: adapter.match(method='--') - except MethodNotAllowed, e: + except MethodNotAllowed as e: methods = e.valid_methods - except HTTPException, e: + except HTTPException as e: pass rv = self.response_class() rv.allow.update(methods) @@ -1626,7 +1626,7 @@ class Flask(_PackageBoundObject): rv = handler(error, endpoint, values) if rv is not None: return rv - except BuildError, error: + except BuildError as error: pass # At this point we want to reraise the exception. If the error is @@ -1807,7 +1807,7 @@ class Flask(_PackageBoundObject): with self.request_context(environ): try: response = self.full_dispatch_request() - except Exception, e: + except Exception as e: response = self.make_response(self.handle_exception(e)) return response(environ, start_response) diff --git a/flask/config.py b/flask/config.py index 759fd488..80e9983e 100644 --- a/flask/config.py +++ b/flask/config.py @@ -127,7 +127,7 @@ class Config(dict): d.__file__ = filename try: execfile(filename, d.__dict__) - except IOError, e: + except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False e.strerror = 'Unable to load configuration file (%s)' % e.strerror diff --git a/flask/ctx.py b/flask/ctx.py index 6b271687..a02c9a86 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -279,7 +279,7 @@ class RequestContext(object): url_rule, self.request.view_args = \ self.url_adapter.match(return_rule=True) self.request.url_rule = url_rule - except HTTPException, e: + except HTTPException as e: self.request.routing_exception = e def push(self): diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 3ebd2f3e..504fab93 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -76,7 +76,7 @@ def attach_enctype_error_multidict(request): def __getitem__(self, key): try: return oldcls.__getitem__(self, key) - except KeyError, e: + except KeyError as e: if key not in request.form: raise raise DebugFilesKeyError(request, key) diff --git a/flask/helpers.py b/flask/helpers.py index d24dde6b..d68cb04b 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -301,7 +301,7 @@ def url_for(endpoint, **values): try: rv = url_adapter.build(endpoint, values, method=method, force_external=external) - except BuildError, error: + except BuildError as error: # We need to inject the values again so that the app callback can # deal with that sort of stuff. values['_external'] = external diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 5d86fa3d..084b3726 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -220,5 +220,5 @@ def main(): """Runs the testsuite as command line application.""" try: unittest.main(testLoader=BetterLoader(), defaultTest='suite') - except Exception, e: + except Exception as e: print 'Error: %s' % e diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 5f6dbe4b..2836a1b6 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -256,7 +256,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def expect_exception(f, *args, **kwargs): try: f(*args, **kwargs) - except RuntimeError, e: + except RuntimeError as e: self.assert_(e.args and 'session is unavailable' in e.args[0]) else: self.assert_(False, 'expected exception') @@ -629,7 +629,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() try: c.get('/fail') - except KeyError, e: + except KeyError as e: self.assert_(isinstance(e, BadRequest)) else: self.fail('Expected exception') @@ -645,7 +645,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() try: c.get('/fail') - except NotFound, e: + except NotFound as e: pass else: self.fail('Expected exception') @@ -664,7 +664,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_client() as c: try: c.post('/fail', data={'foo': 'index.txt'}) - except DebugFilesKeyError, e: + except DebugFilesKeyError as e: self.assert_('no file contents were transmitted' in str(e)) self.assert_('This was submitted: "index.txt"' in str(e)) else: @@ -755,7 +755,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: with app.test_request_context(): flask.url_for('spam') - except BuildError, error: + except BuildError as error: pass try: raise RuntimeError('Test case where BuildError is not current.') @@ -802,7 +802,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return None try: app.test_client().get('/') - except ValueError, e: + except ValueError as e: self.assert_equal(str(e), 'View function did not return a response') pass else: @@ -843,7 +843,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = app.test_client().get('/', 'https://localhost.localdomain') # Werkzeug 0.8 self.assert_equal(rv.status_code, 404) - except ValueError, e: + except ValueError as e: # Werkzeug 0.7 self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:443') does not match the " + \ @@ -854,7 +854,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = app.test_client().get('/', 'http://foo.localhost') # Werkzeug 0.8 self.assert_equal(rv.status_code, 404) - except ValueError, e: + except ValueError as e: # Werkzeug 0.7 self.assert_equal(str(e), "the server name provided " + \ "('localhost.localdomain') does not match the " + \ @@ -975,7 +975,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/foo') def broken(): return 'Meh' - except AssertionError, e: + except AssertionError as e: self.assert_('A setup function was called' in str(e)) else: self.fail('Expected exception') @@ -1009,7 +1009,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_client() as c: try: c.post('/foo', data={}) - except AssertionError, e: + except AssertionError as e: self.assert_('http://localhost/foo/' in str(e)) self.assert_('Make sure to directly send your POST-request ' 'to this URL' in str(e)) diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index ea047918..0399d48e 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -183,7 +183,7 @@ class ModuleTestCase(FlaskTestCase): with app.test_request_context(): try: flask.render_template('missing.html') - except TemplateNotFound, e: + except TemplateNotFound as e: self.assert_equal(e.name, 'missing.html') else: self.assert_(0, 'expected exception') @@ -378,7 +378,7 @@ class BlueprintTestCase(FlaskTestCase): with app.test_request_context(): try: flask.render_template('missing.html') - except TemplateNotFound, e: + except TemplateNotFound as e: self.assert_equal(e.name, 'missing.html') else: self.assert_(0, 'expected exception') diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index bf72925b..0b2c65bd 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -57,7 +57,7 @@ class ConfigTestCase(FlaskTestCase): app = flask.Flask(__name__) try: app.config.from_envvar('FOO_SETTINGS') - except RuntimeError, e: + except RuntimeError as e: self.assert_("'FOO_SETTINGS' is not set" in str(e)) else: self.assert_(0, 'expected exception') @@ -76,7 +76,7 @@ class ConfigTestCase(FlaskTestCase): try: app = flask.Flask(__name__) app.config.from_envvar('FOO_SETTINGS') - except IOError, e: + except IOError as e: msg = str(e) self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' 'file (No such file or directory):')) @@ -91,7 +91,7 @@ class ConfigTestCase(FlaskTestCase): app = flask.Flask(__name__) try: app.config.from_pyfile('missing.cfg') - except IOError, e: + except IOError as e: msg = str(e) self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' 'file (No such file or directory):')) @@ -141,7 +141,7 @@ class InstanceTestCase(FlaskTestCase): here = os.path.abspath(os.path.dirname(__file__)) try: flask.Flask(__name__, instance_path='instance') - except ValueError, e: + except ValueError as e: self.assert_('must be absolute' in str(e)) else: self.fail('Expected value error') diff --git a/flask/testsuite/reqctx.py b/flask/testsuite/reqctx.py index a93523e7..89a47728 100644 --- a/flask/testsuite/reqctx.py +++ b/flask/testsuite/reqctx.py @@ -58,7 +58,7 @@ class RequestContextTestCase(FlaskTestCase): try: with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): pass - except Exception, e: + except Exception as e: self.assert_(isinstance(e, ValueError)) self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:5000') does not match the " + \ @@ -68,7 +68,7 @@ class RequestContextTestCase(FlaskTestCase): app.config.update(SERVER_NAME='localhost') with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}): pass - except ValueError, e: + except ValueError as e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e ) @@ -77,7 +77,7 @@ class RequestContextTestCase(FlaskTestCase): app.config.update(SERVER_NAME='localhost:80') with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}): pass - except ValueError, e: + except ValueError as e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e ) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 92e3f267..605078a7 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -106,7 +106,7 @@ class TestToolsTestCase(FlaskTestCase): try: with c.session_transaction() as sess: pass - except RuntimeError, e: + except RuntimeError as e: self.assert_('Session backend did not open a session' in str(e)) else: self.fail('Expected runtime error') @@ -130,7 +130,7 @@ class TestToolsTestCase(FlaskTestCase): try: with c.session_transaction() as s: pass - except RuntimeError, e: + except RuntimeError as e: self.assert_('cookies' in str(e)) else: self.fail('Expected runtime error') diff --git a/flask/wrappers.py b/flask/wrappers.py index a56fe5d7..717d8ce5 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -101,7 +101,7 @@ class Request(RequestBase): if request_charset is not None: return json.loads(self.data, encoding=request_charset) return json.loads(self.data) - except ValueError, e: + except ValueError as e: return self.on_json_loading_failed(e) def on_json_loading_failed(self, e): From b52b7b1f9322e7ab3e69a387dbfe9fe14de4c401 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 16:31:40 +0200 Subject: [PATCH 0324/2940] automated change using python-modernize: replace execfile --- flask/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/config.py b/flask/config.py index 759fd488..55aef029 100644 --- a/flask/config.py +++ b/flask/config.py @@ -126,7 +126,7 @@ class Config(dict): d = imp.new_module('config') d.__file__ = filename try: - execfile(filename, d.__dict__) + exec(compile(open(filename).read(), filename, 'exec'), d.__dict__) except IOError, e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False From b8b769ad41edeb2320774d88502dd998df272397 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 16:39:39 +0200 Subject: [PATCH 0325/2940] automated change using python-modernize: fix methodattrs --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index cc9a2f90..b7826420 100644 --- a/flask/app.py +++ b/flask/app.py @@ -590,7 +590,7 @@ class Flask(_PackageBoundObject): # Hack to support the init_jinja_globals method which is supported # until 1.0 but has an API deficiency. if getattr(self.init_jinja_globals, 'im_func', None) is not \ - Flask.init_jinja_globals.im_func: + Flask.init_jinja_globals.__func__: from warnings import warn warn(DeprecationWarning('This flask class uses a customized ' 'init_jinja_globals() method which is deprecated. ' From 40fad2ece80e8bf6784e137028645fa66a3cd9c2 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 17:06:25 +0200 Subject: [PATCH 0326/2940] document python 2.6 minimum requirement, remove all stuff that refers to 2.5 --- .travis.yml | 1 - CHANGES | 2 ++ docs/extensiondev.rst | 2 +- docs/installation.rst | 2 +- docs/patterns/jquery.rst | 4 ---- docs/tutorial/dbinit.rst | 7 ++----- flask/json.py | 3 +-- flask/wrappers.py | 2 -- scripts/flask-07-upgrade.py | 4 +--- setup.py | 1 - 10 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd590bea..307945bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - 2.5 - 2.6 - 2.7 - pypy diff --git a/CHANGES b/CHANGES index e383b56f..af3c4752 100644 --- a/CHANGES +++ b/CHANGES @@ -54,6 +54,8 @@ Release date to be decided. - Added `message_flashed` signal that simplifies flashing testing. - Added support for copying of request contexts for better working with greenlets. +- Python requirements changed: requiring Python 2.6 or 2.7 now to prepare + for Python 3.3 port. Version 0.9 ----------- diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 0615e4db..09bf2d2c 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -390,7 +390,7 @@ extension to be approved you have to follow these guidelines: (``PackageName==dev``). 9. The ``zip_safe`` flag in the setup script must be set to ``False``, even if the extension would be safe for zipping. -10. An extension currently has to support Python 2.5, 2.6 as well as +10. An extension currently has to support Python 2.6 as well as Python 2.7 diff --git a/docs/installation.rst b/docs/installation.rst index 5e4673dd..16475383 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -13,7 +13,7 @@ So how do you get all that on your computer quickly? There are many ways you could do that, but the most kick-ass method is virtualenv, so let's have a look at that first. -You will need Python 2.5 or higher to get started, so be sure to have an +You will need Python 2.6 or higher to get started, so be sure to have an up-to-date Python 2.x installation. Python 3.x is not supported. .. _virtualenv: diff --git a/docs/patterns/jquery.rst b/docs/patterns/jquery.rst index 1bd49533..7aaa2803 100644 --- a/docs/patterns/jquery.rst +++ b/docs/patterns/jquery.rst @@ -11,11 +11,7 @@ Python primitives (numbers, strings, dicts and lists) look like which is widely supported and very easy to parse. It became popular a few years ago and quickly replaced XML as transport format in web applications. -If you have Python 2.6 JSON will work out of the box, in Python 2.5 you -will have to install the `simplejson`_ library from PyPI. - .. _jQuery: http://jquery.com/ -.. _simplejson: http://pypi.python.org/pypi/simplejson Loading jQuery -------------- diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 6415cdaa..b32a8eda 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -21,12 +21,9 @@ errors. It's a good idea to add a function that initializes the database for you to the application. If you want to do that, you first have to import the -:func:`contextlib.closing` function from the contextlib package. If you -want to use Python 2.5 it's also necessary to enable the `with` statement -first (`__future__` imports must be the very first import). Accordingly, -add the following lines to your existing imports in `flaskr.py`:: +:func:`contextlib.closing` function from the contextlib package. +Accordingly, add the following lines to your existing imports in `flaskr.py`:: - from __future__ import with_statement from contextlib import closing Next we can create a function called `init_db` that initializes the diff --git a/flask/json.py b/flask/json.py index 717eb2ab..52e5680e 100644 --- a/flask/json.py +++ b/flask/json.py @@ -161,8 +161,7 @@ def jsonify(*args, **kwargs): "id": 42 } - This requires Python 2.6 or an installed version of simplejson. For - security reasons only objects are supported toplevel. For more + For security reasons only objects are supported toplevel. For more information about this, have a look at :ref:`json-security`. .. versionadded:: 0.2 diff --git a/flask/wrappers.py b/flask/wrappers.py index a56fe5d7..d348ee0a 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -92,8 +92,6 @@ class Request(RequestBase): def json(self): """If the mimetype is `application/json` this will contain the parsed JSON data. Otherwise this will be `None`. - - This requires Python 2.6 or an installed version of simplejson. """ if self.mimetype == 'application/json': request_charset = self.mimetype_params.get('charset') diff --git a/scripts/flask-07-upgrade.py b/scripts/flask-07-upgrade.py index 4027d8ce..e1017e69 100644 --- a/scripts/flask-07-upgrade.py +++ b/scripts/flask-07-upgrade.py @@ -287,9 +287,7 @@ def main(): args = ['.'] if ast is None: - parser.error('Python 2.6 or later is required to run the upgrade script.\n' - 'The runtime requirements for Flask 0.7 however are still ' - 'Python 2.5.') + parser.error('Python 2.6 or later is required to run the upgrade script.') for path in args: scan_path(path, teardown=not options.no_teardown) diff --git a/setup.py b/setup.py index 2f9c95ca..ba5f4a67 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,6 @@ setup( 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', From 1b753cb1b1764de5bf3afe1f96032362208febc6 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 17:47:40 +0200 Subject: [PATCH 0327/2940] require 'six' in setup.py, add flask._compat for stuff not yet in 'six' --- flask/_compat.py | 13 +++++++++++++ setup.py | 1 + 2 files changed, 14 insertions(+) create mode 100644 flask/_compat.py diff --git a/flask/_compat.py b/flask/_compat.py new file mode 100644 index 00000000..3743a54c --- /dev/null +++ b/flask/_compat.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +""" + flask._compat + ~~~~~~~~~~~~~ + + Some py2/py3 compatibility support that is not yet available in + "six" 1.3.0. + There are bugs open for "six" for all this stuff, so we can remove it + again from here as soon as we require a new enough "six" release. + + :copyright: (c) 2013 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" diff --git a/setup.py b/setup.py index 2f9c95ca..e25d435f 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,7 @@ setup( zip_safe=False, platforms='any', install_requires=[ + 'six>=1.3.0', 'Werkzeug>=0.7', 'Jinja2>=2.4', 'itsdangerous>=0.17' From cfbfff2d2692a94bdb29133c2e7bb6b792cb337d Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 17:57:43 +0200 Subject: [PATCH 0328/2940] python-modernize automated changes: misc. minor stuff --- flask/testsuite/__init__.py | 3 ++- flask/testsuite/config.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 5d86fa3d..7938a784 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -10,6 +10,7 @@ :license: BSD, see LICENSE for more details. """ +from __future__ import print_function from __future__ import with_statement import os @@ -221,4 +222,4 @@ def main(): try: unittest.main(testLoader=BetterLoader(), defaultTest='suite') except Exception, e: - print 'Error: %s' % e + print('Error: %s' % e) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index bf72925b..2027f578 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -113,7 +113,7 @@ class LimitedLoaderMockWrapper(object): def __getattr__(self, name): if name in ('archive', 'get_filename'): msg = 'Mocking a loader which does not have `%s.`' % name - raise AttributeError, msg + raise AttributeError(msg) return getattr(self.loader, name) From 0f8c47c988fbecc30785959065a53e00861c8558 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 18:00:15 +0200 Subject: [PATCH 0329/2940] python-modernize automated changes: fix_dict --- flask/sessions.py | 3 ++- flask/templating.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 31b5900b..3746da7f 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -17,6 +17,7 @@ from werkzeug.datastructures import CallbackDict from . import Markup, json from itsdangerous import URLSafeTimedSerializer, BadSignature +import six def total_seconds(td): @@ -68,7 +69,7 @@ class TaggedJSONSerializer(object): elif isinstance(value, datetime): return {' d': http_date(value)} elif isinstance(value, dict): - return dict((k, _tag(v)) for k, v in value.iteritems()) + return dict((k, _tag(v)) for k, v in six.iteritems(value)) elif isinstance(value, str): try: return unicode(value) diff --git a/flask/templating.py b/flask/templating.py index 2cc09c4d..754c6893 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -15,6 +15,7 @@ from jinja2 import BaseLoader, Environment as BaseEnvironment, \ from .globals import _request_ctx_stack, _app_ctx_stack from .signals import template_rendered from .module import blueprint_is_module +import six def _default_template_ctx_processor(): @@ -79,7 +80,7 @@ class DispatchingJinjaLoader(BaseLoader): except (ValueError, KeyError): pass - for blueprint in self.app.blueprints.itervalues(): + for blueprint in six.itervalues(self.app.blueprints): if blueprint_is_module(blueprint): continue loader = blueprint.jinja_loader @@ -92,7 +93,7 @@ class DispatchingJinjaLoader(BaseLoader): if loader is not None: result.update(loader.list_templates()) - for name, blueprint in self.app.blueprints.iteritems(): + for name, blueprint in six.iteritems(self.app.blueprints): loader = blueprint.jinja_loader if loader is not None: for template in loader.list_templates(): From dcd052366b43df7bfe730c2e991ff37c617ba52b Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 18:03:37 +0200 Subject: [PATCH 0330/2940] python-modernize automated changes: fix_next --- flask/helpers.py | 3 ++- flask/sessions.py | 2 +- flask/testsuite/helpers.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index d24dde6b..a302995c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -26,6 +26,7 @@ from functools import update_wrapper from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound +import six # this was moved in 0.7 try: @@ -128,7 +129,7 @@ def stream_with_context(generator_or_function): # pushed. This item is discarded. Then when the iteration continues the # real generator is executed. wrapped_g = generator() - wrapped_g.next() + six.advance_iterator(wrapped_g) return wrapped_g diff --git a/flask/sessions.py b/flask/sessions.py index 3746da7f..b8e37014 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -85,7 +85,7 @@ class TaggedJSONSerializer(object): def object_hook(obj): if len(obj) != 1: return obj - the_key, the_value = obj.iteritems().next() + the_key, the_value = six.advance_iterator(obj.iteritems()) if the_key == ' t': return tuple(the_value) elif the_key == ' u': diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index fdf2d89f..5604f85f 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -18,6 +18,7 @@ from logging import StreamHandler from StringIO import StringIO from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr from werkzeug.http import parse_cache_control_header, parse_options_header +import six def has_encoding(name): @@ -507,7 +508,7 @@ class StreamingTestCase(FlaskTestCase): def close(self): called.append(42) def next(self): - return self._gen.next() + return six.advance_iterator(self._gen) @app.route('/') def index(): def generate(): From 522cd0009367d1f775b0e978681426773f51ce14 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 18:12:30 +0200 Subject: [PATCH 0331/2940] python-modernize automated changes: fix_unicode (but without six.u()) --- flask/json.py | 3 ++- flask/sessions.py | 4 ++-- flask/testsuite/basic.py | 3 ++- flask/testsuite/blueprints.py | 3 ++- flask/testsuite/helpers.py | 6 +++--- flask/testsuite/testing.py | 3 ++- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/flask/json.py b/flask/json.py index 717eb2ab..dee2e189 100644 --- a/flask/json.py +++ b/flask/json.py @@ -17,6 +17,7 @@ from werkzeug.http import http_date # Use the same json implementation as itsdangerous on which we # depend anyways. from itsdangerous import simplejson as _json +import six # figure out if simplejson escapes slashes. This behavior was changed @@ -59,7 +60,7 @@ class JSONEncoder(_json.JSONEncoder): if isinstance(o, uuid.UUID): return str(o) if hasattr(o, '__html__'): - return unicode(o.__html__()) + return six.text_type(o.__html__()) return _json.JSONEncoder.default(self, o) diff --git a/flask/sessions.py b/flask/sessions.py index b8e37014..04b7f8af 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -63,7 +63,7 @@ class TaggedJSONSerializer(object): elif isinstance(value, uuid.UUID): return {' u': value.hex} elif callable(getattr(value, '__html__', None)): - return {' m': unicode(value.__html__())} + return {' m': six.text_type(value.__html__())} elif isinstance(value, list): return [_tag(x) for x in value] elif isinstance(value, datetime): @@ -72,7 +72,7 @@ class TaggedJSONSerializer(object): return dict((k, _tag(v)) for k, v in six.iteritems(value)) elif isinstance(value, str): try: - return unicode(value) + return six.text_type(value) except UnicodeError: raise UnexpectedUnicodeError(u'A byte string with ' u'non-ASCII data was passed to the session system ' diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 5f6dbe4b..7cdf2211 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -22,6 +22,7 @@ from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_date from werkzeug.routing import BuildError +import six class BasicFunctionalityTestCase(FlaskTestCase): @@ -277,7 +278,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/test') def test(): - return unicode(flask.session.permanent) + return six.text_type(flask.session.permanent) client = app.test_client() rv = client.get('/') diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index ea047918..97426c8e 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -18,6 +18,7 @@ from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning from werkzeug.exceptions import NotFound from werkzeug.http import parse_cache_control_header from jinja2 import TemplateNotFound +import six # import moduleapp here because it uses deprecated features and we don't @@ -304,7 +305,7 @@ class BlueprintTestCase(FlaskTestCase): @bp.route('/bar') def bar(bar): - return unicode(bar) + return six.text_type(bar) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 5604f85f..ac618538 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -36,7 +36,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/json', methods=['POST']) def return_json(): - return unicode(flask.request.json) + return six.text_type(flask.request.json) c = app.test_client() rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) @@ -45,7 +45,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/json', methods=['POST']) def return_json(): - return unicode(flask.request.json) + return six.text_type(flask.request.json) c = app.test_client() rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) @@ -97,7 +97,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/add', methods=['POST']) def add(): - return unicode(flask.request.json['a'] + flask.request.json['b']) + return six.text_type(flask.request.json['a'] + flask.request.json['b']) c = app.test_client() rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), content_type='application/json') diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 92e3f267..92be9d33 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -14,6 +14,7 @@ from __future__ import with_statement import flask import unittest from flask.testsuite import FlaskTestCase +import six class TestToolsTestCase(FlaskTestCase): @@ -85,7 +86,7 @@ class TestToolsTestCase(FlaskTestCase): @app.route('/') def index(): - return unicode(flask.session['foo']) + return six.text_type(flask.session['foo']) with app.test_client() as c: with c.session_transaction() as sess: From 323a840c5ab39e1df889d93ad0da28c5bd553c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 18 May 2013 18:27:49 +0200 Subject: [PATCH 0332/2940] Add tox.ini --- tox.ini | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..56c8f393 --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[tox] +envlist = py26, py27, pypy, py33 + +[testenv] +commands = python run-tests.py [] From aba1d3a5074953702f1ddc9fe623d2a724dd6570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 18 May 2013 18:28:59 +0200 Subject: [PATCH 0333/2940] Test on 3.3 on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 307945bf..f17e99b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - 2.6 - 2.7 - pypy + - 3.3 before_install: pip install simplejson From 287905e67c2f22df3c3055a0cca025931726cd02 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 18 May 2013 18:39:10 +0200 Subject: [PATCH 0334/2940] py3 compat: use six.reload_module --- flask/testsuite/ext.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 034ab5be..096a70f8 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -13,7 +13,7 @@ from __future__ import with_statement import sys import unittest from flask.testsuite import FlaskTestCase - +from six import reload_module class ExtImportHookTestCase(FlaskTestCase): @@ -29,7 +29,7 @@ class ExtImportHookTestCase(FlaskTestCase): entry == 'flaskext') and value is not None: sys.modules.pop(entry, None) from flask import ext - reload(ext) + reload_module(ext) # reloading must not add more hooks import_hooks = 0 From c618db92d6059ab1262ea2450b0f81a94cdad901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 18 May 2013 18:55:36 +0200 Subject: [PATCH 0335/2940] reload_module is in six.moves --- flask/testsuite/ext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 096a70f8..6f6a34f4 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -13,7 +13,7 @@ from __future__ import with_statement import sys import unittest from flask.testsuite import FlaskTestCase -from six import reload_module +from six.moves import reload_module class ExtImportHookTestCase(FlaskTestCase): From 506db0eab2bf9e514177d8b46e2708f10601cf36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 18 May 2013 19:00:06 +0200 Subject: [PATCH 0336/2940] Use print_function --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4ad0b546..1d3e36ad 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ Links `_ """ +from __future__ import print_function from setuptools import Command, setup class run_audit(Command): @@ -59,7 +60,7 @@ class run_audit(Command): try: import pyflakes.scripts.pyflakes as flakes except ImportError: - print "Audit requires PyFlakes installed in your system." + print("Audit requires PyFlakes installed in your system.") sys.exit(-1) warns = 0 @@ -71,9 +72,9 @@ class run_audit(Command): if file != '__init__.py' and file.endswith('.py') : warns += flakes.checkPath(os.path.join(root, file)) if warns > 0: - print "Audit finished with total %d warnings." % warns + print("Audit finished with total %d warnings." % warns) else: - print "No problems found in sourcecode." + print("No problems found in sourcecode.") setup( name='Flask', From ceb7c7f7717c13cdb9c6d2eccb135a9edc71eef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sat, 18 May 2013 19:05:10 +0200 Subject: [PATCH 0337/2940] Don't notify IRC for this branch --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f17e99b8..33027fef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,9 +19,3 @@ notifications: # their own builder failure from us. Travis currently fails way # too many times by itself. email: false - - irc: - channels: - - "irc.freenode.org#pocoo" - use_notice: true - skip_join: true From 81c9b3570b70c092a2428fe9f21fb897ea5486b1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 20 May 2013 09:44:05 +0100 Subject: [PATCH 0338/2940] Removed 2.5 from travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fd590bea..307945bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - 2.5 - 2.6 - 2.7 - pypy From aecc41deb8d5cb8a1505be52648c78432e9238ae Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 20 May 2013 09:47:07 +0100 Subject: [PATCH 0339/2940] Restore 2.5 support for the time being --- .travis.yml | 1 + flask/ctx.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 307945bf..fd590bea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: + - 2.5 - 2.6 - 2.7 - pypy diff --git a/flask/ctx.py b/flask/ctx.py index 6b271687..0340486b 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -9,6 +9,8 @@ :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement + import sys from functools import update_wrapper From a503520ac51d16558ce91f7d7fc9d30a910d928c Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 21 May 2013 23:34:25 +0200 Subject: [PATCH 0340/2940] copy _compat.py from flask in here (and adapt docstring) --- flask/_compat.py | 104 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index 3743a54c..776a2dd7 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -3,11 +3,107 @@ flask._compat ~~~~~~~~~~~~~ - Some py2/py3 compatibility support that is not yet available in - "six" 1.3.0. - There are bugs open for "six" for all this stuff, so we can remove it - again from here as soon as we require a new enough "six" release. + Some py2/py3 compatibility support based on a stripped down + version of six so we don't have to depend on a specific version + of it. :copyright: (c) 2013 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import sys + +PY2 = sys.version_info[0] == 2 +PYPY = hasattr(sys, 'pypy_translation_info') +_identity = lambda x: x + + +if not PY2: + unichr = chr + range_type = range + text_type = str + string_types = (str,) + + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + + import pickle + from io import BytesIO, StringIO + NativeStringIO = StringIO + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + ifilter = filter + imap = map + izip = zip + intern = sys.intern + + implements_iterator = _identity + implements_to_string = _identity + encode_filename = _identity + get_next = lambda x: x.__next__ + +else: + unichr = unichr + text_type = unicode + range_type = xrange + string_types = (str, unicode) + + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() + + import cPickle as pickle + from cStringIO import StringIO as BytesIO, StringIO + NativeStringIO = BytesIO + + exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') + + from itertools import imap, izip, ifilter + intern = intern + + def implements_iterator(cls): + cls.next = cls.__next__ + del cls.__next__ + return cls + + def implements_to_string(cls): + cls.__unicode__ = cls.__str__ + cls.__str__ = lambda x: x.__unicode__().encode('utf-8') + return cls + + get_next = lambda x: x.next + + def encode_filename(filename): + if isinstance(filename, unicode): + return filename.encode('utf-8') + return filename + + +def with_metaclass(meta, *bases): + # This requires a bit of explanation: the basic idea is to make a + # dummy metaclass for one level of class instanciation that replaces + # itself with the actual metaclass. Because of internal type checks + # we also need to make sure that we downgrade the custom metaclass + # for one level to something closer to type (that's why __call__ and + # __init__ comes back from type etc.). + # + # This has the advantage over six.with_metaclass in that it does not + # introduce dummy classes into the final MRO. + class metaclass(meta): + __call__ = type.__call__ + __init__ = type.__init__ + def __new__(cls, name, this_bases, d): + if this_bases is None: + return type.__new__(cls, name, (), d) + return meta(name, bases, d) + return metaclass('temporary_class', None, {}) + + +try: + from urllib.parse import quote_from_bytes as url_quote +except ImportError: + from urllib import quote as url_quote From e1d356fb713f3272db2a23f9f898c34c5dc79dc0 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 22 May 2013 01:33:04 +0200 Subject: [PATCH 0341/2940] ported some more stuff to py 3.3 removed init_jinja_globals hack from app.py after consulting mitsuhiko (didn't work on py 3.3 "as is") removed with_statement future imports, not needed any more needs more work on 2.7 as well as on 3.3 --- examples/flaskr/flaskr.py | 2 +- examples/minitwit/minitwit.py | 2 +- flask/_compat.py | 6 ++++++ flask/app.py | 33 +++++++++------------------------ flask/config.py | 5 ++--- flask/exthook.py | 3 ++- flask/helpers.py | 9 ++++----- flask/testing.py | 4 +--- flask/testsuite/__init__.py | 5 ++--- flask/testsuite/appctx.py | 2 -- flask/testsuite/basic.py | 4 +--- flask/testsuite/blueprints.py | 2 -- flask/testsuite/config.py | 1 - flask/testsuite/deprecations.py | 19 +------------------ flask/testsuite/ext.py | 5 ++--- flask/testsuite/helpers.py | 4 +--- flask/testsuite/regression.py | 4 +--- flask/testsuite/reqctx.py | 2 -- flask/testsuite/signals.py | 1 - flask/testsuite/subclassing.py | 2 +- flask/testsuite/templating.py | 2 -- flask/testsuite/testing.py | 2 -- flask/testsuite/views.py | 2 +- scripts/flaskext_test.py | 2 -- 24 files changed, 36 insertions(+), 87 deletions(-) diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 0647fc7b..20254660 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -9,7 +9,7 @@ :copyright: (c) 2010 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement + from sqlite3 import dbapi2 as sqlite3 from flask import Flask, request, session, g, redirect, url_for, abort, \ render_template, flash, _app_ctx_stack diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index a92da6af..2863de50 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -8,7 +8,7 @@ :copyright: (c) 2010 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement + import time from sqlite3 import dbapi2 as sqlite3 from hashlib import md5 diff --git a/flask/_compat.py b/flask/_compat.py index 776a2dd7..27f61137 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -22,6 +22,7 @@ if not PY2: range_type = range text_type = str string_types = (str,) + integer_types = (int, ) iterkeys = lambda d: iter(d.keys()) itervalues = lambda d: iter(d.values()) @@ -46,11 +47,14 @@ if not PY2: encode_filename = _identity get_next = lambda x: x.__next__ + from urllib.parse import urlparse + else: unichr = unichr text_type = unicode range_type = xrange string_types = (str, unicode) + integer_types = (int, long) iterkeys = lambda d: d.iterkeys() itervalues = lambda d: d.itervalues() @@ -82,6 +86,8 @@ else: return filename.encode('utf-8') return filename + from urlparse import urlparse + def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a diff --git a/flask/app.py b/flask/app.py index 0337c608..dc684489 100644 --- a/flask/app.py +++ b/flask/app.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import os import sys from threading import Lock @@ -36,6 +34,7 @@ from .templating import DispatchingJinjaLoader, Environment, \ _default_template_ctx_processor from .signals import request_started, request_finished, got_request_exception, \ request_tearing_down, appcontext_tearing_down +from flask._compat import reraise, string_types, integer_types # a lock used for logger initialization _logger_lock = Lock() @@ -585,21 +584,7 @@ class Flask(_PackageBoundObject): @locked_cached_property def jinja_env(self): """The Jinja2 environment used to load templates.""" - rv = self.create_jinja_environment() - - # Hack to support the init_jinja_globals method which is supported - # until 1.0 but has an API deficiency. - if getattr(self.init_jinja_globals, 'im_func', None) is not \ - Flask.init_jinja_globals.__func__: - from warnings import warn - warn(DeprecationWarning('This flask class uses a customized ' - 'init_jinja_globals() method which is deprecated. ' - 'Move the code from that method into the ' - 'create_jinja_environment() method instead.')) - self.__dict__['jinja_env'] = rv - self.init_jinja_globals() - - return rv + return self.create_jinja_environment() @property def got_first_request(self): @@ -1090,7 +1075,7 @@ class Flask(_PackageBoundObject): def _register_error_handler(self, key, code_or_exception, f): if isinstance(code_or_exception, HTTPException): code_or_exception = code_or_exception.code - if isinstance(code_or_exception, (int, long)): + if isinstance(code_or_exception, integer_types): assert code_or_exception != 500 or key is None, \ 'It is currently not possible to register a 500 internal ' \ 'server error on a per-blueprint level.' @@ -1137,7 +1122,7 @@ class Flask(_PackageBoundObject): def is_prime(n): if n == 2: return True - for i in xrange(2, int(math.ceil(math.sqrt(n))) + 1): + for i in range(2, int(math.ceil(math.sqrt(n))) + 1): if n % i == 0: return False return True @@ -1383,7 +1368,7 @@ class Flask(_PackageBoundObject): if isinstance(e, typecheck): return handler(e) - raise exc_type, exc_value, tb + reraise(exc_type, exc_value, tb) def handle_exception(self, e): """Default exception handling that kicks in when an exception @@ -1405,7 +1390,7 @@ class Flask(_PackageBoundObject): # (the function was actually called from the except part) # otherwise, we just raise the error again if exc_value is e: - raise exc_type, exc_value, tb + reraise(exc_type, exc_value, tb) else: raise e @@ -1565,14 +1550,14 @@ class Flask(_PackageBoundObject): # set the headers and status. We do this because there can be # some extra logic involved when creating these objects with # specific values (like defualt content type selection). - if isinstance(rv, basestring): + if isinstance(rv, string_types): rv = self.response_class(rv, headers=headers, status=status) headers = status = None else: rv = self.response_class.force_type(rv, request.environ) if status is not None: - if isinstance(status, basestring): + if isinstance(status, string_types): rv.status = status else: rv.status_code = status @@ -1633,7 +1618,7 @@ class Flask(_PackageBoundObject): # still the same one we can reraise it with the original traceback, # otherwise we raise it from here. if error is exc_value: - raise exc_type, exc_value, tb + reraise(exc_type, exc_value, tb) raise error def preprocess_request(self): diff --git a/flask/config.py b/flask/config.py index 3afe623a..ddb113a5 100644 --- a/flask/config.py +++ b/flask/config.py @@ -9,13 +9,12 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import imp import os import errno from werkzeug.utils import import_string +from flask._compat import string_types class ConfigAttribute(object): @@ -158,7 +157,7 @@ class Config(dict): :param obj: an import name or object """ - if isinstance(obj, basestring): + if isinstance(obj, string_types): obj = import_string(obj) for key in dir(obj): if key.isupper(): diff --git a/flask/exthook.py b/flask/exthook.py index 26578f0f..89dac47b 100644 --- a/flask/exthook.py +++ b/flask/exthook.py @@ -21,6 +21,7 @@ """ import sys import os +from flask._compat import reraise class ExtensionImporter(object): @@ -77,7 +78,7 @@ class ExtensionImporter(object): # we swallow it and try the next choice. The skipped frame # is the one from __import__ above which we don't care about if self.is_important_traceback(realname, tb): - raise exc_type, exc_value, tb.tb_next + reraise(exc_type, exc_value, tb.tb_next) continue module = sys.modules[fullname] = sys.modules[realname] if '.' not in modname: diff --git a/flask/helpers.py b/flask/helpers.py index 68ebc74e..1359bba6 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import os import sys import pkgutil @@ -27,6 +25,7 @@ from functools import update_wrapper from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound import six +from flask._compat import string_types, text_type # this was moved in 0.7 try: @@ -467,7 +466,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, :data:`~flask.current_app`. """ mtime = None - if isinstance(filename_or_fp, basestring): + if isinstance(filename_or_fp, string_types): filename = filename_or_fp file = None else: @@ -478,7 +477,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, # 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, basestring): + 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.'), @@ -540,7 +539,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, os.path.getmtime(filename), os.path.getsize(filename), adler32( - filename.encode('utf-8') if isinstance(filename, unicode) + filename.encode('utf-8') if isinstance(filename, text_type) else filename ) & 0xffffffff )) diff --git a/flask/testing.py b/flask/testing.py index bdd3860f..ef116cf3 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,12 +10,10 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack -from urlparse import urlparse +from flask._compat import urlparse def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 9027369c..82fa3232 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -11,17 +11,16 @@ """ from __future__ import print_function -from __future__ import with_statement import os import sys import flask import warnings import unittest -from StringIO import StringIO from functools import update_wrapper from contextlib import contextmanager from werkzeug.utils import import_string, find_modules +from flask._compat import reraise, StringIO def add_to_path(path): @@ -159,7 +158,7 @@ class _ExceptionCatcher(object): self.test_case.fail('Expected exception of type %r' % exception_name) elif not issubclass(exc_type, self.exc_type): - raise exc_type, exc_value, tb + reraise(exc_type, exc_value, tb) return True diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index aa71e11e..afed923a 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import flask import unittest from flask.testsuite import FlaskTestCase diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 89159a16..974b4ebc 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import re import uuid import flask @@ -1060,7 +1058,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1/0 c = app.test_client() - for x in xrange(3): + for x in range(3): with self.assert_raises(ZeroDivisionError): c.get('/fail') diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index eba43e63..331854a6 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import flask import unittest import warnings diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index ca142cff..415d8aca 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -8,7 +8,6 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement import os import sys diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py index 795a5d3d..56371822 100644 --- a/flask/testsuite/deprecations.py +++ b/flask/testsuite/deprecations.py @@ -9,30 +9,13 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import flask import unittest from flask.testsuite import FlaskTestCase, catch_warnings class DeprecationsTestCase(FlaskTestCase): - - def test_init_jinja_globals(self): - class MyFlask(flask.Flask): - def init_jinja_globals(self): - self.jinja_env.globals['foo'] = '42' - - with catch_warnings() as log: - app = MyFlask(__name__) - @app.route('/') - def foo(): - return app.jinja_env.globals['foo'] - - c = app.test_client() - self.assert_equal(c.get('/').data, '42') - self.assert_equal(len(log), 1) - self.assert_('init_jinja_globals' in str(log[0]['message'])) + """not used currently""" def suite(): diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 6f6a34f4..147e23b0 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -8,7 +8,6 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement import sys import unittest @@ -22,7 +21,7 @@ class ExtImportHookTestCase(FlaskTestCase): # that a real flaskext could be in there which would disable our # fake package. Secondly we want to make sure that the flaskext # import hook does not break on reloading. - for entry, value in sys.modules.items(): + for entry, value in list(sys.modules.items()): if (entry.startswith('flask.ext.') or entry.startswith('flask_') or entry.startswith('flaskext.') or @@ -100,7 +99,7 @@ class ExtImportHookTestCase(FlaskTestCase): self.assert_equal(test_function(), 42) def test_flaskext_broken_package_no_module_caching(self): - for x in xrange(2): + for x in range(2): with self.assert_raises(ImportError): import flask.ext.broken diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index ac618538..2808a409 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -9,16 +9,14 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import os import flask import unittest from logging import StreamHandler -from StringIO import StringIO from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr from werkzeug.http import parse_cache_control_header, parse_options_header import six +from flask._compat import StringIO def has_encoding(name): diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py index 00219856..b8140d6f 100644 --- a/flask/testsuite/regression.py +++ b/flask/testsuite/regression.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import os import gc import sys @@ -77,7 +75,7 @@ class MemoryTestCase(FlaskTestCase): if sys.version_info >= (2, 7) and \ not hasattr(sys, 'pypy_translation_info'): with self.assert_no_leak(): - for x in xrange(10): + for x in range(10): fire() def test_safe_join_toplevel_pardir(self): diff --git a/flask/testsuite/reqctx.py b/flask/testsuite/reqctx.py index 89a47728..0f0282e5 100644 --- a/flask/testsuite/reqctx.py +++ b/flask/testsuite/reqctx.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import flask import unittest try: diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index 0e5d0cea..637c2c66 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -8,7 +8,6 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement import flask import unittest diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py index 89aa9150..3e8d75c1 100644 --- a/flask/testsuite/subclassing.py +++ b/flask/testsuite/subclassing.py @@ -11,9 +11,9 @@ """ import flask import unittest -from StringIO import StringIO from logging import StreamHandler from flask.testsuite import FlaskTestCase +from flask._compat import StringIO class FlaskSubclassingTestCase(FlaskTestCase): diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 635210f7..c8d1dd22 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import flask import unittest from flask.testsuite import FlaskTestCase diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 2872ecdc..a37f6e6c 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import flask import unittest from flask.testsuite import FlaskTestCase diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py index f09c1266..6c3ae816 100644 --- a/flask/testsuite/views.py +++ b/flask/testsuite/views.py @@ -8,7 +8,7 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement + import flask import flask.views import unittest diff --git a/scripts/flaskext_test.py b/scripts/flaskext_test.py index d1d5d991..5b0d9d22 100644 --- a/scripts/flaskext_test.py +++ b/scripts/flaskext_test.py @@ -9,8 +9,6 @@ :license: BSD, see LICENSE for more details. """ -from __future__ import with_statement - import os import sys import shutil From da5edad23a13336d218ee08f9b8843f611aded07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 16:19:57 +0200 Subject: [PATCH 0342/2940] Use werkzeug@sprint-branch in tox --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 56c8f393..161e86fb 100644 --- a/tox.ini +++ b/tox.ini @@ -2,4 +2,5 @@ envlist = py26, py27, pypy, py33 [testenv] +deps = -egit+git://github.com/mitsuhiko/werkzeug.git@sprint-branch#egg=werkzeug commands = python run-tests.py [] From 884aad8ecebe309d3b17502ecaa64008d2b461e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 16:33:05 +0200 Subject: [PATCH 0343/2940] Test using itsdangerous with 3.x support --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 161e86fb..c699ac48 100644 --- a/tox.ini +++ b/tox.ini @@ -3,4 +3,5 @@ envlist = py26, py27, pypy, py33 [testenv] deps = -egit+git://github.com/mitsuhiko/werkzeug.git@sprint-branch#egg=werkzeug + -egit+git://github.com/mitsuhiko/itsdangerous.git#egg=itsdangerous commands = python run-tests.py [] From 3f51a09db4673c45f19cf1dcdbe04212ac7de890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 16:33:50 +0200 Subject: [PATCH 0344/2940] itsdangerous uses json instead of simplejson now --- flask/json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/json.py b/flask/json.py index d5da4f91..6b95fde0 100644 --- a/flask/json.py +++ b/flask/json.py @@ -16,7 +16,7 @@ from werkzeug.http import http_date # Use the same json implementation as itsdangerous on which we # depend anyways. -from itsdangerous import simplejson as _json +from itsdangerous import json as _json import six From 05f66ad73543db207791ea5d6a2de2e8eaaf1640 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 22 May 2013 17:05:48 +0200 Subject: [PATCH 0345/2940] Fix some literals --- flask/testsuite/basic.py | 111 +++++++++++++++++----------------- flask/testsuite/blueprints.py | 74 +++++++++++------------ flask/testsuite/helpers.py | 16 ++--- flask/testsuite/regression.py | 4 +- flask/testsuite/reqctx.py | 4 +- flask/testsuite/signals.py | 4 +- flask/testsuite/templating.py | 16 ++--- flask/testsuite/testing.py | 8 +-- flask/testsuite/views.py | 14 ++--- 9 files changed, 125 insertions(+), 126 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 974b4ebc..1636bee7 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -32,7 +32,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'Hello World' rv = app.test_client().open('/', method='OPTIONS') self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) - self.assert_equal(rv.data, '') + self.assert_equal(rv.data, b'') def test_options_on_multiple_rules(self): app = flask.Flask(__name__) @@ -72,15 +72,15 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.request.method c = app.test_client() - self.assert_equal(c.get('/').data, 'GET') + self.assert_equal(c.get('/').data, b'GET') rv = c.post('/') self.assert_equal(rv.status_code, 405) self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) self.assert_(not rv.data) # head truncates - self.assert_equal(c.post('/more').data, 'POST') - self.assert_equal(c.get('/more').data, 'GET') + self.assert_equal(c.post('/more').data, b'POST') + self.assert_equal(c.get('/more').data, b'GET') rv = c.delete('/more') self.assert_equal(rv.status_code, 405) self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) @@ -96,15 +96,15 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) c = app.test_client() - self.assert_equal(c.get('/').data, 'GET') + self.assert_equal(c.get('/').data, b'GET') rv = c.post('/') self.assert_equal(rv.status_code, 405) self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) self.assert_(not rv.data) # head truncates - self.assert_equal(c.post('/more').data, 'POST') - self.assert_equal(c.get('/more').data, 'GET') + self.assert_equal(c.post('/more').data, b'POST') + self.assert_equal(c.get('/more').data, b'GET') rv = c.delete('/more') self.assert_equal(rv.status_code, 405) self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) @@ -124,8 +124,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.view_functions['index'] = index c = app.test_client() - self.assert_equal(c.get('/foo/').data, 'index') - self.assert_equal(c.get('/foo/bar').data, 'bar') + self.assert_equal(c.get('/foo/').data, b'index') + self.assert_equal(c.get('/foo/bar').data, b'bar') def test_endpoint_decorator(self): from werkzeug.routing import Submount, Rule @@ -144,8 +144,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'index' c = app.test_client() - self.assert_equal(c.get('/foo/').data, 'index') - self.assert_equal(c.get('/foo/bar').data, 'bar') + self.assert_equal(c.get('/foo/').data, b'index') + self.assert_equal(c.get('/foo/bar').data, b'bar') def test_session(self): app = flask.Flask(__name__) @@ -159,8 +159,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.session['value'] c = app.test_client() - self.assert_equal(c.post('/set', data={'value': '42'}).data, 'value set') - self.assert_equal(c.get('/get').data, '42') + self.assert_equal(c.post('/set', data={'value': '42'}).data, b'value set') + self.assert_equal(c.get('/get').data, b'42') def test_session_using_server_name(self): app = flask.Flask(__name__) @@ -289,7 +289,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(expires.day, expected.day) rv = client.get('/test') - self.assert_equal(rv.data, 'True') + self.assert_equal(rv.data, b'True') permanent = False rv = app.test_client().get('/') @@ -311,8 +311,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return repr(flask.session.get('foo')) c = app.test_client() - self.assert_equal(c.get('/').data, 'None') - self.assert_equal(c.get('/').data, '42') + self.assert_equal(c.get('/').data, b'None') + self.assert_equal(c.get('/').data, b'42') def test_session_special_types(self): app = flask.Flask(__name__) @@ -454,7 +454,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_('after' not in evts) rv = app.test_client().get('/').data self.assert_('after' in evts) - self.assert_equal(rv, 'request|after') + self.assert_equal(rv, b'request|after') def test_after_request_processing(self): app = flask.Flask(__name__) @@ -483,7 +483,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - self.assert_('Response' in rv.data) + self.assert_(b'Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): @@ -499,7 +499,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - self.assert_('Response' in rv.data) + self.assert_(b'Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): @@ -532,7 +532,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_('Internal Server Error' in rv.data) + self.assert_(b'Internal Server Error' in rv.data) self.assert_equal(len(called), 2) def test_before_after_request_order(self): @@ -562,7 +562,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(): return '42' rv = app.test_client().get('/') - self.assert_equal(rv.data, '42') + self.assert_equal(rv.data, b'42') self.assert_equal(called, [1, 2, 3, 4, 5, 6]) def test_error_handling(self): @@ -582,10 +582,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') self.assert_equal(rv.status_code, 404) - self.assert_equal(rv.data, 'not found') + self.assert_equal(rv.data, b'not found') rv = c.get('/error') self.assert_equal(rv.status_code, 500) - self.assert_equal('internal server error', rv.data) + self.assert_equal(b'internal server error', rv.data) def test_before_request_and_routing_errors(self): app = flask.Flask(__name__) @@ -597,7 +597,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.g.something, 404 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 404) - self.assert_equal(rv.data, 'value') + self.assert_equal(rv.data, b'value') def test_user_error_handling(self): class MyException(Exception): @@ -613,7 +613,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): raise MyException() c = app.test_client() - self.assert_equal(c.get('/').data, '42') + self.assert_equal(c.get('/').data, b'42') def test_trapping_of_bad_request_key_errors(self): app = flask.Flask(__name__) @@ -687,7 +687,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(c.get('/unicode').data, u'Hällo Wörld'.encode('utf-8')) self.assert_equal(c.get('/string').data, u'Hällo Wörld'.encode('utf-8')) rv = c.get('/args') - self.assert_equal(rv.data, 'Meh') + self.assert_equal(rv.data, b'Meh') self.assert_equal(rv.headers['X-Foo'], 'Testing') self.assert_equal(rv.status_code, 400) self.assert_equal(rv.mimetype, 'text/plain') @@ -697,17 +697,17 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_request_context(): rv = flask.make_response() self.assert_equal(rv.status_code, 200) - self.assert_equal(rv.data, '') + self.assert_equal(rv.data, b'') self.assert_equal(rv.mimetype, 'text/html') rv = flask.make_response('Awesome') self.assert_equal(rv.status_code, 200) - self.assert_equal(rv.data, 'Awesome') + self.assert_equal(rv.data, b'Awesome') self.assert_equal(rv.mimetype, 'text/html') rv = flask.make_response('W00t', 404) self.assert_equal(rv.status_code, 404) - self.assert_equal(rv.data, 'W00t') + self.assert_equal(rv.data, b'W00t') self.assert_equal(rv.mimetype, 'text/html') def test_make_response_with_response_instance(self): @@ -716,14 +716,13 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = flask.make_response( flask.jsonify({'msg': 'W00t'}), 400) self.assertEqual(rv.status_code, 400) - self.assertEqual(rv.data, - '{\n "msg": "W00t"\n}') + self.assertEqual(rv.data, b'{\n "msg": "W00t"\n}') self.assertEqual(rv.mimetype, 'application/json') rv = flask.make_response( flask.Response(''), 400) self.assertEqual(rv.status_code, 400) - self.assertEqual(rv.data, '') + self.assertEqual(rv.data, b'') self.assertEqual(rv.mimetype, 'text/html') rv = flask.make_response( @@ -783,13 +782,13 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(args): return '|'.join(args) c = app.test_client() - self.assert_equal(c.get('/1,2,3').data, '1|2|3') + self.assert_equal(c.get('/1,2,3').data, b'1|2|3') def test_static_files(self): app = flask.Flask(__name__) rv = app.test_client().get('/static/index.html') self.assert_equal(rv.status_code, 200) - self.assert_equal(rv.data.strip(), '

Hello World!

') + self.assert_equal(rv.data.strip(), b'

Hello World!

') with app.test_request_context(): self.assert_equal(flask.url_for('static', filename='index.html'), '/static/index.html') @@ -825,17 +824,17 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'Foo SubDomain' rv = app.test_client().get('/') - self.assert_equal(rv.data, 'Foo') + self.assert_equal(rv.data, b'Foo') rv = app.test_client().get('/', 'http://localhost.localdomain:5000') - self.assert_equal(rv.data, 'Foo') + self.assert_equal(rv.data, b'Foo') rv = app.test_client().get('/', 'https://localhost.localdomain:5000') - self.assert_equal(rv.data, 'Foo') + self.assert_equal(rv.data, b'Foo') app.config.update(SERVER_NAME='localhost.localdomain') rv = app.test_client().get('/', 'https://localhost.localdomain') - self.assert_equal(rv.data, 'Foo') + self.assert_equal(rv.data, b'Foo') try: app.config.update(SERVER_NAME='localhost.localdomain:443') @@ -860,7 +859,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): "server name from the WSGI environment ('foo.localhost')") rv = app.test_client().get('/', 'http://foo.localhost.localdomain') - self.assert_equal(rv.data, 'Foo SubDomain') + self.assert_equal(rv.data, b'Foo SubDomain') def test_exception_propagation(self): def apprunner(configkey): @@ -906,7 +905,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() rv = c.post('/accept', data={'myfile': 'foo' * 100}) - self.assert_equal(rv.data, '42') + self.assert_equal(rv.data, b'42') def test_url_processors(self): app = flask.Flask(__name__) @@ -935,9 +934,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() - self.assert_equal(c.get('/de/').data, '/de/about') - self.assert_equal(c.get('/de/about').data, '/foo') - self.assert_equal(c.get('/foo').data, '/en/about') + self.assert_equal(c.get('/de/').data, b'/de/about') + self.assert_equal(c.get('/de/about').data, b'/foo') + self.assert_equal(c.get('/foo').data, b'/en/about') def test_inject_blueprint_url_defaults(self): app = flask.Flask(__name__) @@ -969,7 +968,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(): return 'Awesome' self.assert_(not app.got_first_request) - self.assert_equal(app.test_client().get('/').data, 'Awesome') + self.assert_equal(app.test_client().get('/').data, b'Awesome') try: @app.route('/foo') def broken(): @@ -983,7 +982,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/foo') def working(): return 'Meh' - self.assert_equal(app.test_client().get('/foo').data, 'Meh') + self.assert_equal(app.test_client().get('/foo').data, b'Meh') self.assert_(app.got_first_request) def test_before_first_request_functions(self): @@ -1016,12 +1015,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.fail('Expected exception') rv = c.get('/foo', data={}, follow_redirects=True) - self.assert_equal(rv.data, 'success') + self.assert_equal(rv.data, b'success') app.debug = False with app.test_client() as c: rv = c.post('/foo', data={}, follow_redirects=True) - self.assert_equal(rv.data, 'success') + self.assert_equal(rv.data, b'success') def test_route_decorator_custom_endpoint(self): app = flask.Flask(__name__) @@ -1045,9 +1044,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): assert flask.url_for('123') == '/bar/123' c = app.test_client() - self.assertEqual(c.get('/foo/').data, 'foo') - self.assertEqual(c.get('/bar/').data, 'bar') - self.assertEqual(c.get('/bar/123').data, '123') + self.assertEqual(c.get('/foo/').data, b'foo') + self.assertEqual(c.get('/bar/').data, b'bar') + self.assertEqual(c.get('/bar/123').data, b'123') def test_preserve_only_once(self): app = flask.Flask(__name__) @@ -1084,10 +1083,10 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://localhost/') - self.assert_equal(rv.data, 'normal index') + self.assert_equal(rv.data, b'normal index') rv = c.get('/', 'http://test.localhost/') - self.assert_equal(rv.data, 'test index') + self.assert_equal(rv.data, b'test index') @emits_module_deprecation_warning def test_module_static_path_subdomain(self): @@ -1108,7 +1107,7 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://mitsuhiko.localhost/') - self.assert_equal(rv.data, 'index for mitsuhiko') + self.assert_equal(rv.data, b'index for mitsuhiko') def test_subdomain_matching_with_ports(self): app = flask.Flask(__name__) @@ -1119,7 +1118,7 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://mitsuhiko.localhost:3000/') - self.assert_equal(rv.data, 'index for mitsuhiko') + self.assert_equal(rv.data, b'index for mitsuhiko') @emits_module_deprecation_warning def test_module_subdomain_support(self): @@ -1139,9 +1138,9 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/test', 'http://testing.localhost/') - self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.data, b'Test') rv = c.get('/outside', 'http://xtesting.localhost/') - self.assert_equal(rv.data, 'Outside') + self.assert_equal(rv.data, b'Outside') def suite(): diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 331854a6..28256b63 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -46,10 +46,10 @@ class ModuleTestCase(FlaskTestCase): return 'the index' app.register_module(admin) c = app.test_client() - self.assert_equal(c.get('/').data, 'the index') - self.assert_equal(c.get('/admin/').data, 'admin index') - self.assert_equal(c.get('/admin/login').data, 'admin login') - self.assert_equal(c.get('/admin/logout').data, 'admin logout') + self.assert_equal(c.get('/').data, b'the index') + self.assert_equal(c.get('/admin/').data, b'admin index') + self.assert_equal(c.get('/admin/login').data, b'admin login') + self.assert_equal(c.get('/admin/logout').data, b'admin logout') @emits_module_deprecation_warning def test_default_endpoint_name(self): @@ -60,7 +60,7 @@ class ModuleTestCase(FlaskTestCase): mod.add_url_rule('/', view_func=index) app.register_module(mod) rv = app.test_client().get('/') - self.assert_equal(rv.data, 'Awesome') + self.assert_equal(rv.data, b'Awesome') with app.test_request_context(): self.assert_equal(flask.url_for('frontend.index'), '/') @@ -92,11 +92,11 @@ class ModuleTestCase(FlaskTestCase): app.register_module(admin) c = app.test_client() - self.assert_equal(c.get('/').data, 'the index') + self.assert_equal(c.get('/').data, b'the index') self.assert_equal(catched, ['before-app', 'after-app']) del catched[:] - self.assert_equal(c.get('/admin/').data, 'the admin') + self.assert_equal(c.get('/admin/').data, b'the admin') self.assert_equal(catched, ['before-app', 'before-admin', 'after-admin', 'after-app']) @@ -121,8 +121,8 @@ class ModuleTestCase(FlaskTestCase): return flask.render_template_string('{{ a }}{{ b }}{{ c }}') app.register_module(admin) c = app.test_client() - self.assert_equal(c.get('/').data, '13') - self.assert_equal(c.get('/admin/').data, '123') + self.assert_equal(c.get('/').data, b'13') + self.assert_equal(c.get('/admin/').data, b'123') @emits_module_deprecation_warning def test_late_binding(self): @@ -132,7 +132,7 @@ class ModuleTestCase(FlaskTestCase): def index(): return '42' app.register_module(admin, url_prefix='/admin') - self.assert_equal(app.test_client().get('/admin/').data, '42') + self.assert_equal(app.test_client().get('/admin/').data, b'42') @emits_module_deprecation_warning def test_error_handling(self): @@ -154,7 +154,7 @@ class ModuleTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') self.assert_equal(rv.status_code, 404) - self.assert_equal(rv.data, 'not found') + self.assert_equal(rv.data, b'not found') rv = c.get('/error') self.assert_equal(rv.status_code, 500) self.assert_equal('internal server error', rv.data) @@ -165,11 +165,11 @@ class ModuleTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') - self.assert_equal(rv.data, 'Hello from the Frontend') + self.assert_equal(rv.data, b'Hello from the Frontend') rv = c.get('/admin/') - self.assert_equal(rv.data, 'Hello from the Admin') + self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/index2') - self.assert_equal(rv.data, 'Hello from the Admin') + self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/static/test.txt') self.assert_equal(rv.data.strip(), 'Admin File') rv = c.get('/admin/static/css/test.css') @@ -248,8 +248,8 @@ class ModuleTestCase(FlaskTestCase): app.register_module(module) c = app.test_client() - self.assert_equal(c.get('/foo/').data, 'index') - self.assert_equal(c.get('/foo/bar').data, 'bar') + self.assert_equal(c.get('/foo/').data, b'index') + self.assert_equal(c.get('/foo/bar').data, b'bar') class BlueprintTestCase(FlaskTestCase): @@ -290,9 +290,9 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() - self.assert_equal(c.get('/frontend-no').data, 'frontend says no') - self.assert_equal(c.get('/backend-no').data, 'backend says no') - self.assert_equal(c.get('/what-is-a-sideend').data, 'application itself says no') + self.assert_equal(c.get('/frontend-no').data, b'frontend says no') + self.assert_equal(c.get('/backend-no').data, b'backend says no') + self.assert_equal(c.get('/what-is-a-sideend').data, b'application itself says no') def test_blueprint_url_definitions(self): bp = flask.Blueprint('test', __name__) @@ -339,19 +339,19 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() - self.assert_equal(c.get('/de/').data, '/de/about') - self.assert_equal(c.get('/de/about').data, '/de/') + self.assert_equal(c.get('/de/').data, b'/de/about') + self.assert_equal(c.get('/de/about').data, b'/de/') def test_templates_and_static(self): from blueprintapp import app c = app.test_client() rv = c.get('/') - self.assert_equal(rv.data, 'Hello from the Frontend') + self.assert_equal(rv.data, b'Hello from the Frontend') rv = c.get('/admin/') - self.assert_equal(rv.data, 'Hello from the Admin') + self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/index2') - self.assert_equal(rv.data, 'Hello from the Admin') + self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/static/test.txt') self.assert_equal(rv.data.strip(), 'Admin File') rv = c.get('/admin/static/css/test.css') @@ -451,8 +451,8 @@ class BlueprintTestCase(FlaskTestCase): app.register_blueprint(bp) c = app.test_client() - self.assert_equal(c.get('/').data, '1') - self.assert_equal(c.get('/page/2').data, '2') + self.assert_equal(c.get('/').data, b'1') + self.assert_equal(c.get('/page/2').data, b'2') def test_route_decorator_custom_endpoint(self): @@ -482,11 +482,11 @@ class BlueprintTestCase(FlaskTestCase): return flask.request.endpoint c = app.test_client() - self.assertEqual(c.get('/').data, 'index') - self.assertEqual(c.get('/py/foo').data, 'bp.foo') - self.assertEqual(c.get('/py/bar').data, 'bp.bar') - self.assertEqual(c.get('/py/bar/123').data, 'bp.123') - self.assertEqual(c.get('/py/bar/foo').data, 'bp.bar_foo') + self.assertEqual(c.get('/').data, b'index') + self.assertEqual(c.get('/py/foo').data, b'bp.foo') + self.assertEqual(c.get('/py/bar').data, b'bp.bar') + self.assertEqual(c.get('/py/bar/123').data, b'bp.123') + self.assertEqual(c.get('/py/bar/foo').data, b'bp.bar_foo') def test_route_decorator_custom_endpoint_with_dots(self): bp = flask.Blueprint('bp', __name__) @@ -533,7 +533,7 @@ class BlueprintTestCase(FlaskTestCase): app.register_blueprint(bp, url_prefix='/py') c = app.test_client() - self.assertEqual(c.get('/py/foo').data, 'bp.foo') + self.assertEqual(c.get('/py/foo').data, b'bp.foo') # The rule's din't actually made it through rv = c.get('/py/bar') assert rv.status_code == 404 @@ -595,7 +595,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_template_filter_after_route_with_template(self): app = flask.Flask(__name__) @@ -608,7 +608,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app.register_blueprint(bp, url_prefix='/py') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_add_template_filter_with_template(self): bp = flask.Blueprint('bp', __name__) @@ -621,7 +621,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_template_filter_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -634,7 +634,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_add_template_filter_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -647,7 +647,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_template_test(self): bp = flask.Blueprint('bp', __name__) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 2808a409..fbdcbff4 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -99,7 +99,7 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), content_type='application/json') - self.assert_equal(rv.data, '3') + self.assert_equal(rv.data, b'3') def test_template_escaping(self): app = flask.Flask(__name__) @@ -140,7 +140,7 @@ class JSONTestCase(FlaskTestCase): rv = c.post('/', data=flask.json.dumps({ 'x': {'_foo': 42} }), content_type='application/json') - self.assertEqual(rv.data, '"<42>"') + self.assertEqual(rv.data, b'"<42>"') def test_modified_url_encoding(self): class ModifiedRequest(flask.Request): @@ -212,14 +212,14 @@ class SendfileTestCase(FlaskTestCase): with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f) - self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.data, b'Test') self.assert_equal(rv.mimetype, 'application/octet-stream') # etags self.assert_equal(len(captured), 1) with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f, mimetype='text/plain') - self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.data, b'Test') self.assert_equal(rv.mimetype, 'text/plain') # etags self.assert_equal(len(captured), 1) @@ -385,7 +385,7 @@ class LoggingTestCase(FlaskTestCase): for trigger in 'before', 'after': rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_equal(rv.data, 'Hello Server Error') + self.assert_equal(rv.data, b'Hello Server Error') def test_url_for_with_anchor(self): app = flask.Flask(__name__) @@ -477,7 +477,7 @@ class StreamingTestCase(FlaskTestCase): return flask.Response(flask.stream_with_context(generate())) c = app.test_client() rv = c.get('/?name=World') - self.assertEqual(rv.data, 'Hello World!') + self.assertEqual(rv.data, b'Hello World!') def test_streaming_with_context_as_decorator(self): app = flask.Flask(__name__) @@ -492,7 +492,7 @@ class StreamingTestCase(FlaskTestCase): return flask.Response(generate()) c = app.test_client() rv = c.get('/?name=World') - self.assertEqual(rv.data, 'Hello World!') + self.assertEqual(rv.data, b'Hello World!') def test_streaming_with_context_and_custom_close(self): app = flask.Flask(__name__) @@ -517,7 +517,7 @@ class StreamingTestCase(FlaskTestCase): Wrapper(generate()))) c = app.test_client() rv = c.get('/?name=World') - self.assertEqual(rv.data, 'Hello World!') + self.assertEqual(rv.data, b'Hello World!') self.assertEqual(called, [42]) diff --git a/flask/testsuite/regression.py b/flask/testsuite/regression.py index b8140d6f..e516dc0f 100644 --- a/flask/testsuite/regression.py +++ b/flask/testsuite/regression.py @@ -66,7 +66,7 @@ class MemoryTestCase(FlaskTestCase): with app.test_client() as c: rv = c.get('/') self.assert_equal(rv.status_code, 200) - self.assert_equal(rv.data, '

42

') + self.assert_equal(rv.data, b'

42

') # Trigger caches fire() @@ -105,7 +105,7 @@ class ExceptionTestCase(FlaskTestCase): rv = c.get('/') self.assertEqual(rv.headers['Location'], 'http://localhost/test') rv = c.get('/test') - self.assertEqual(rv.data, '42') + self.assertEqual(rv.data, b'42') def suite(): diff --git a/flask/testsuite/reqctx.py b/flask/testsuite/reqctx.py index 0f0282e5..6e379448 100644 --- a/flask/testsuite/reqctx.py +++ b/flask/testsuite/reqctx.py @@ -145,7 +145,7 @@ class RequestContextTestCase(FlaskTestCase): return 'Hello World!' rv = app.test_client().get('/?foo=bar') - self.assert_equal(rv.data, 'Hello World!') + self.assert_equal(rv.data, b'Hello World!') result = greenlets[0].run() self.assert_equal(result, 42) @@ -168,7 +168,7 @@ class RequestContextTestCase(FlaskTestCase): return 'Hello World!' rv = app.test_client().get('/?foo=bar') - self.assert_equal(rv.data, 'Hello World!') + self.assert_equal(rv.data, b'Hello World!') result = greenlets[0].run() self.assert_equal(result, 42) diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index 637c2c66..843c2f83 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -45,7 +45,7 @@ class SignalsTestCase(FlaskTestCase): calls.append('before-signal') def after_request_signal(sender, response): - self.assert_equal(response.data, 'stuff') + self.assert_equal(response.data, b'stuff') calls.append('after-signal') @app.before_request @@ -68,7 +68,7 @@ class SignalsTestCase(FlaskTestCase): try: rv = app.test_client().get('/') - self.assert_equal(rv.data, 'stuff') + self.assert_equal(rv.data, b'stuff') self.assert_equal(calls, ['before-signal', 'before-handler', 'handler', 'after-handler', diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index c8d1dd22..c36a282e 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -25,7 +25,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('context_template.html', value=23) rv = app.test_client().get('/') - self.assert_equal(rv.data, '

23|42') + self.assert_equal(rv.data, b'

23|42') def test_original_win(self): app = flask.Flask(__name__) @@ -33,7 +33,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template_string('{{ config }}', config=42) rv = app.test_client().get('/') - self.assert_equal(rv.data, '42') + self.assert_equal(rv.data, b'42') def test_request_less_rendering(self): app = flask.Flask(__name__) @@ -139,7 +139,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_add_template_filter_with_template(self): app = flask.Flask(__name__) @@ -150,7 +150,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_template_filter_with_name_and_template(self): app = flask.Flask(__name__) @@ -161,7 +161,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_add_template_filter_with_name_and_template(self): app = flask.Flask(__name__) @@ -172,7 +172,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - self.assert_equal(rv.data, 'dcba') + self.assert_equal(rv.data, b'dcba') def test_template_test(self): app = flask.Flask(__name__) @@ -277,7 +277,7 @@ class TemplatingTestCase(FlaskTestCase): return flask.render_template('index.html') c = app.test_client() rv = c.get('/') - self.assert_equal(rv.data, 'Hello Custom World!') + self.assert_equal(rv.data, b'Hello Custom World!') def test_iterable_loader(self): app = flask.Flask(__name__) @@ -293,7 +293,7 @@ class TemplatingTestCase(FlaskTestCase): value=23) rv = app.test_client().get('/') - self.assert_equal(rv.data, '

Jameson

') + self.assert_equal(rv.data, b'

Jameson

') def suite(): diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index a37f6e6c..8b1b7d62 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -30,7 +30,7 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(ctx.request.url, 'http://example.com:1234/foo/') with app.test_client() as c: rv = c.get('/') - self.assert_equal(rv.data, 'http://example.com:1234/foo/') + self.assert_equal(rv.data, b'http://example.com:1234/foo/') def test_environ_defaults(self): app = flask.Flask(__name__) @@ -43,7 +43,7 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(ctx.request.url, 'http://localhost/') with app.test_client() as c: rv = c.get('/') - self.assert_equal(rv.data, 'http://localhost/') + self.assert_equal(rv.data, b'http://localhost/') def test_redirect_keep_session(self): app = flask.Flask(__name__) @@ -92,7 +92,7 @@ class TestToolsTestCase(FlaskTestCase): sess['foo'] = [42] self.assert_equal(len(sess), 1) rv = c.get('/') - self.assert_equal(rv.data, '[42]') + self.assert_equal(rv.data, b'[42]') with c.session_transaction() as sess: self.assert_equal(len(sess), 1) self.assert_equal(sess['foo'], [42]) @@ -148,7 +148,7 @@ class TestToolsTestCase(FlaskTestCase): with app.test_client() as c: resp = c.get('/') self.assert_equal(flask.g.value, 42) - self.assert_equal(resp.data, 'Hello World!') + self.assert_equal(resp.data, b'Hello World!') self.assert_equal(resp.status_code, 200) resp = c.get('/other') diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py index 6c3ae816..9dd463f2 100644 --- a/flask/testsuite/views.py +++ b/flask/testsuite/views.py @@ -20,8 +20,8 @@ class ViewTestCase(FlaskTestCase): def common_test(self, app): c = app.test_client() - self.assert_equal(c.get('/').data, 'GET') - self.assert_equal(c.post('/').data, 'POST') + self.assert_equal(c.get('/').data, b'GET') + self.assert_equal(c.post('/').data, b'POST') self.assert_equal(c.put('/').status_code, 405) meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) self.assert_equal(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST']) @@ -108,7 +108,7 @@ class ViewTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') self.assert_equal(rv.headers['X-Parachute'], 'awesome') - self.assert_equal(rv.data, 'Awesome') + self.assert_equal(rv.data, b'Awesome') def test_implicit_head(self): app = flask.Flask(__name__) @@ -122,10 +122,10 @@ class ViewTestCase(FlaskTestCase): app.add_url_rule('/', view_func=Index.as_view('index')) c = app.test_client() rv = c.get('/') - self.assert_equal(rv.data, 'Blub') + self.assert_equal(rv.data, b'Blub') self.assert_equal(rv.headers['X-Method'], 'GET') rv = c.head('/') - self.assert_equal(rv.data, '') + self.assert_equal(rv.data, b'') self.assert_equal(rv.headers['X-Method'], 'HEAD') def test_explicit_head(self): @@ -140,9 +140,9 @@ class ViewTestCase(FlaskTestCase): app.add_url_rule('/', view_func=Index.as_view('index')) c = app.test_client() rv = c.get('/') - self.assert_equal(rv.data, 'GET') + self.assert_equal(rv.data, b'GET') rv = c.head('/') - self.assert_equal(rv.data, '') + self.assert_equal(rv.data, b'') self.assert_equal(rv.headers['X-Method'], 'HEAD') def test_endpoint_override(self): From 5b89355b1c9771431fb8a011094591ad43461b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 17:14:07 +0200 Subject: [PATCH 0346/2940] Response data is bytes --- flask/testsuite/basic.py | 8 ++++---- flask/testsuite/blueprints.py | 34 +++++++++++++++++----------------- flask/testsuite/helpers.py | 2 +- flask/testsuite/subclassing.py | 2 +- flask/testsuite/templating.py | 22 +++++++++++----------- flask/testsuite/testing.py | 14 +++++++------- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 1636bee7..c76204d7 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -453,7 +453,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'request' self.assert_('after' not in evts) rv = app.test_client().get('/').data - self.assert_('after' in evts) + self.assert_(b'after' in evts) self.assert_equal(rv, b'request|after') def test_after_request_processing(self): @@ -937,10 +937,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(c.get('/de/').data, b'/de/about') self.assert_equal(c.get('/de/about').data, b'/foo') self.assert_equal(c.get('/foo').data, b'/en/about') - + def test_inject_blueprint_url_defaults(self): app = flask.Flask(__name__) - bp = flask.Blueprint('foo.bar.baz', __name__, + bp = flask.Blueprint('foo.bar.baz', __name__, template_folder='template') @bp.url_defaults @@ -1096,7 +1096,7 @@ class SubdomainTestCase(FlaskTestCase): app.register_module(mod) c = app.test_client() rv = c.get('/static/hello.txt', 'http://foo.example.com/') - self.assert_equal(rv.data.strip(), 'Hello Subdomain') + self.assert_equal(rv.data.strip(), b'Hello Subdomain') def test_subdomain_matching(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 28256b63..ed3ddbb2 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -157,7 +157,7 @@ class ModuleTestCase(FlaskTestCase): self.assert_equal(rv.data, b'not found') rv = c.get('/error') self.assert_equal(rv.status_code, 500) - self.assert_equal('internal server error', rv.data) + self.assert_equal(b'internal server error', rv.data) def test_templates_and_static(self): app = moduleapp @@ -171,9 +171,9 @@ class ModuleTestCase(FlaskTestCase): rv = c.get('/admin/index2') self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/static/test.txt') - self.assert_equal(rv.data.strip(), 'Admin File') + self.assert_equal(rv.data.strip(), b'Admin File') rv = c.get('/admin/static/css/test.css') - self.assert_equal(rv.data.strip(), '/* nested file */') + self.assert_equal(rv.data.strip(), b'/* nested file */') with app.test_request_context(): self.assert_equal(flask.url_for('admin.static', filename='test.txt'), @@ -310,10 +310,10 @@ class BlueprintTestCase(FlaskTestCase): app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) c = app.test_client() - self.assert_equal(c.get('/1/foo').data, u'23/42') - self.assert_equal(c.get('/2/foo').data, u'19/42') - self.assert_equal(c.get('/1/bar').data, u'23') - self.assert_equal(c.get('/2/bar').data, u'19') + self.assert_equal(c.get('/1/foo').data, b'23/42') + self.assert_equal(c.get('/2/foo').data, b'19/42') + self.assert_equal(c.get('/1/bar').data, b'23') + self.assert_equal(c.get('/2/bar').data, b'19') def test_blueprint_url_processors(self): bp = flask.Blueprint('frontend', __name__, url_prefix='/') @@ -353,9 +353,9 @@ class BlueprintTestCase(FlaskTestCase): rv = c.get('/admin/index2') self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/static/test.txt') - self.assert_equal(rv.data.strip(), 'Admin File') + self.assert_equal(rv.data.strip(), b'Admin File') rv = c.get('/admin/static/css/test.css') - self.assert_equal(rv.data.strip(), '/* nested file */') + self.assert_equal(rv.data.strip(), b'/* nested file */') # try/finally, in case other tests use this app for Blueprint tests. max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT'] @@ -435,9 +435,9 @@ class BlueprintTestCase(FlaskTestCase): app.register_blueprint(backend) c = app.test_client() - self.assert_equal(c.get('/fe').data.strip(), '/be') - self.assert_equal(c.get('/fe2').data.strip(), '/fe') - self.assert_equal(c.get('/be').data.strip(), '/fe') + self.assert_equal(c.get('/fe').data.strip(), b'/be') + self.assert_equal(c.get('/fe2').data.strip(), b'/fe') + self.assert_equal(c.get('/be').data.strip(), b'/fe') def test_empty_url_defaults(self): bp = flask.Blueprint('bp', __name__) @@ -704,7 +704,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_template_test_after_route_with_template(self): app = flask.Flask(__name__) @@ -717,7 +717,7 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app.register_blueprint(bp, url_prefix='/py') rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_add_template_test_with_template(self): bp = flask.Blueprint('bp', __name__) @@ -730,7 +730,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_template_test_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -743,7 +743,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_add_template_test_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -756,7 +756,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index fbdcbff4..9ca9eec3 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -357,7 +357,7 @@ class LoggingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_('Internal Server Error' in rv.data) + self.assert_(b'Internal Server Error' in rv.data) err = out.getvalue() self.assert_('Exception on / [GET]' in err) diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py index 3e8d75c1..e26b9085 100644 --- a/flask/testsuite/subclassing.py +++ b/flask/testsuite/subclassing.py @@ -34,7 +34,7 @@ class FlaskSubclassingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_('Internal Server Error' in rv.data) + self.assert_(b'Internal Server Error' in rv.data) err = out.getvalue() self.assert_equal(err, '') diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index c36a282e..0d3a2a5c 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -61,7 +61,7 @@ class TemplatingTestCase(FlaskTestCase): {{ session.test }} ''') rv = app.test_client().get('/?foo=42') - self.assert_equal(rv.data.split(), ['42', '23', 'False', 'aha']) + self.assert_equal(rv.data.split(), [b'42', b'23', b'False', b'aha']) def test_escaping(self): text = '

Hello World!' @@ -72,12 +72,12 @@ class TemplatingTestCase(FlaskTestCase): html=flask.Markup(text)) lines = app.test_client().get('/').data.splitlines() self.assert_equal(lines, [ - '<p>Hello World!', - '

Hello World!', - '

Hello World!', - '

Hello World!', - '<p>Hello World!', - '

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

Hello World!', + b'

Hello World!', + b'

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

Hello World!' ]) def test_no_escaping(self): @@ -219,7 +219,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_add_template_test_with_template(self): app = flask.Flask(__name__) @@ -230,7 +230,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_template_test_with_name_and_template(self): app = flask.Flask(__name__) @@ -241,7 +241,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_add_template_test_with_name_and_template(self): app = flask.Flask(__name__) @@ -252,7 +252,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_('Success!' in rv.data) + self.assert_(b'Success!' in rv.data) def test_add_template_global(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 8b1b7d62..3379a463 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -62,20 +62,20 @@ class TestToolsTestCase(FlaskTestCase): with app.test_client() as c: rv = c.get('/getsession') - assert rv.data == '' + assert rv.data == b'' rv = c.get('/') - assert rv.data == 'index' + assert rv.data == b'index' assert flask.session.get('data') == 'foo' rv = c.post('/', data={}, follow_redirects=True) - assert rv.data == 'foo' + assert rv.data == b'foo' # This support requires a new Werkzeug version if not hasattr(c, 'redirect_client'): assert flask.session.get('data') == 'foo' rv = c.get('/getsession') - assert rv.data == 'foo' + assert rv.data == b'foo' def test_session_transactions(self): app = flask.Flask(__name__) @@ -153,7 +153,7 @@ class TestToolsTestCase(FlaskTestCase): resp = c.get('/other') self.assert_(not hasattr(flask.g, 'value')) - self.assert_('Internal Server Error' in resp.data) + self.assert_(b'Internal Server Error' in resp.data) self.assert_equal(resp.status_code, 500) flask.g.value = 23 @@ -220,7 +220,7 @@ class SubdomainTestCase(FlaskTestCase): response = self.client.get(url) self.assertEquals(200, response.status_code) - self.assertEquals('xxx', response.data) + self.assertEquals(b'xxx', response.data) def test_nosubdomain(self): @@ -232,7 +232,7 @@ class SubdomainTestCase(FlaskTestCase): response = self.client.get(url) self.assertEquals(200, response.status_code) - self.assertEquals('xxx', response.data) + self.assertEquals(b'xxx', response.data) def suite(): From 239780be289efd365c07be2f60bab9223388399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 17:23:38 +0200 Subject: [PATCH 0347/2940] Use assert_true instead of assert_ assert_ is deprecated which causes annoying warnings --- flask/testsuite/__init__.py | 7 ++- flask/testsuite/appctx.py | 2 +- flask/testsuite/basic.py | 92 +++++++++++++++++----------------- flask/testsuite/blueprints.py | 44 ++++++++-------- flask/testsuite/config.py | 24 ++++----- flask/testsuite/ext.py | 8 +-- flask/testsuite/helpers.py | 38 +++++++------- flask/testsuite/reqctx.py | 24 ++++----- flask/testsuite/signals.py | 2 +- flask/testsuite/subclassing.py | 2 +- flask/testsuite/templating.py | 36 ++++++------- flask/testsuite/testing.py | 12 ++--- 12 files changed, 147 insertions(+), 144 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 82fa3232..2a869173 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -101,9 +101,9 @@ def emits_module_deprecation_warning(f): def new_f(self, *args, **kwargs): with catch_warnings() as log: f(self, *args, **kwargs) - self.assert_(log, 'expected deprecation warning') + self.assert_true(log, 'expected deprecation warning') for entry in log: - self.assert_('Modules are deprecated' in str(entry['message'])) + self.assert_true('Modules are deprecated' in str(entry['message'])) return update_wrapper(new_f, f) @@ -142,6 +142,9 @@ class FlaskTestCase(unittest.TestCase): with catcher: callable(*args, **kwargs) + def assert_true(self, x): + self.assertTrue(x) + class _ExceptionCatcher(object): diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index afed923a..54b9014e 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -87,7 +87,7 @@ class AppContextTestCase(FlaskTestCase): with flask._app_ctx_stack.top: with flask._request_ctx_stack.top: pass - self.assert_(flask._request_ctx_stack.request.environ + self.assert_true(flask._request_ctx_stack.request.environ ['werkzeug.request'] is not None) c = app.test_client() c.get('/') diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index c76204d7..20c2f0cc 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -78,7 +78,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - self.assert_(not rv.data) # head truncates + self.assert_true(not rv.data) # head truncates self.assert_equal(c.post('/more').data, b'POST') self.assert_equal(c.get('/more').data, b'GET') rv = c.delete('/more') @@ -102,7 +102,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - self.assert_(not rv.data) # head truncates + self.assert_true(not rv.data) # head truncates self.assert_equal(c.post('/more').data, b'POST') self.assert_equal(c.get('/more').data, b'GET') rv = c.delete('/more') @@ -173,8 +173,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com/') - self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower()) - self.assert_('httponly' in rv.headers['set-cookie'].lower()) + self.assert_true('domain=.example.com' in rv.headers['set-cookie'].lower()) + self.assert_true('httponly' in rv.headers['set-cookie'].lower()) def test_session_using_server_name_and_port(self): app = flask.Flask(__name__) @@ -187,8 +187,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower()) - self.assert_('httponly' in rv.headers['set-cookie'].lower()) + self.assert_true('domain=.example.com' in rv.headers['set-cookie'].lower()) + self.assert_true('httponly' in rv.headers['set-cookie'].lower()) def test_session_using_server_name_port_and_path(self): app = flask.Flask(__name__) @@ -202,9 +202,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/foo') - self.assert_('domain=example.com' in rv.headers['set-cookie'].lower()) - self.assert_('path=/foo' in rv.headers['set-cookie'].lower()) - self.assert_('httponly' in rv.headers['set-cookie'].lower()) + self.assert_true('domain=example.com' in rv.headers['set-cookie'].lower()) + self.assert_true('path=/foo' in rv.headers['set-cookie'].lower()) + self.assert_true('httponly' in rv.headers['set-cookie'].lower()) def test_session_using_application_root(self): class PrefixPathMiddleware(object): @@ -226,7 +226,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - self.assert_('path=/bar' in rv.headers['set-cookie'].lower()) + self.assert_true('path=/bar' in rv.headers['set-cookie'].lower()) def test_session_using_session_settings(self): app = flask.Flask(__name__) @@ -245,10 +245,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'Hello World' rv = app.test_client().get('/', 'http://www.example.com:8080/test/') cookie = rv.headers['set-cookie'].lower() - self.assert_('domain=.example.com' in cookie) - self.assert_('path=/;' in cookie) - self.assert_('secure' in cookie) - self.assert_('httponly' not in cookie) + self.assert_true('domain=.example.com' in cookie) + self.assert_true('path=/;' in cookie) + self.assert_true('secure' in cookie) + self.assert_true('httponly' not in cookie) def test_missing_session(self): app = flask.Flask(__name__) @@ -256,11 +256,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: f(*args, **kwargs) except RuntimeError as e: - self.assert_(e.args and 'session is unavailable' in e.args[0]) + self.assert_true(e.args and 'session is unavailable' in e.args[0]) else: - self.assert_(False, 'expected exception') + self.assert_true(False, 'expected exception') with app.test_request_context(): - self.assert_(flask.session.get('missing_key') is None) + self.assert_true(flask.session.get('missing_key') is None) expect_exception(flask.session.__setitem__, 'foo', 42) expect_exception(flask.session.pop, 'foo') @@ -280,7 +280,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): client = app.test_client() rv = client.get('/') - self.assert_('set-cookie' in rv.headers) + self.assert_true('set-cookie' in rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime @@ -293,9 +293,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): permanent = False rv = app.test_client().get('/') - self.assert_('set-cookie' in rv.headers) + self.assert_true('set-cookie' in rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) - self.assert_(match is None) + self.assert_true(match is None) def test_session_stored_last(self): app = flask.Flask(__name__) @@ -347,11 +347,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.secret_key = 'testkey' with app.test_request_context(): - self.assert_(not flask.session.modified) + self.assert_true(not flask.session.modified) flask.flash('Zap') flask.session.modified = False flask.flash('Zip') - self.assert_(flask.session.modified) + self.assert_true(flask.session.modified) self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): @@ -448,12 +448,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): return response @app.route('/') def index(): - self.assert_('before' in evts) - self.assert_('after' not in evts) + self.assert_true('before' in evts) + self.assert_true('after' not in evts) return 'request' - self.assert_('after' not in evts) + self.assert_true('after' not in evts) rv = app.test_client().get('/').data - self.assert_(b'after' in evts) + self.assert_true(b'after' in evts) self.assert_equal(rv, b'request|after') def test_after_request_processing(self): @@ -483,7 +483,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - self.assert_(b'Response' in rv.data) + self.assert_true(b'Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): @@ -499,7 +499,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - self.assert_(b'Response' in rv.data) + self.assert_true(b'Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): @@ -532,7 +532,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_(b'Internal Server Error' in rv.data) + self.assert_true(b'Internal Server Error' in rv.data) self.assert_equal(len(called), 2) def test_before_after_request_order(self): @@ -606,7 +606,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.errorhandler(MyException) def handle_my_exception(e): - self.assert_(isinstance(e, MyException)) + self.assert_true(isinstance(e, MyException)) return '42' @app.route('/') def index(): @@ -629,7 +629,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.get('/fail') except KeyError as e: - self.assert_(isinstance(e, BadRequest)) + self.assert_true(isinstance(e, BadRequest)) else: self.fail('Expected exception') @@ -664,8 +664,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.post('/fail', data={'foo': 'index.txt'}) except DebugFilesKeyError as e: - self.assert_('no file contents were transmitted' in str(e)) - self.assert_('This was submitted: "index.txt"' in str(e)) + self.assert_true('no file contents were transmitted' in str(e)) + self.assert_true('This was submitted: "index.txt"' in str(e)) else: self.fail('Expected exception') @@ -804,7 +804,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(str(e), 'View function did not return a response') pass else: - self.assert_("Expected ValueError") + self.assert_true("Expected ValueError") def test_request_locals(self): self.assert_equal(repr(flask.g), '') @@ -894,11 +894,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.before_request def always_first(): flask.request.form['myfile'] - self.assert_(False) + self.assert_true(False) @app.route('/accept', methods=['POST']) def accept_file(): flask.request.form['myfile'] - self.assert_(False) + self.assert_true(False) @app.errorhandler(413) def catcher(error): return '42' @@ -967,14 +967,14 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/') def index(): return 'Awesome' - self.assert_(not app.got_first_request) + self.assert_true(not app.got_first_request) self.assert_equal(app.test_client().get('/').data, b'Awesome') try: @app.route('/foo') def broken(): return 'Meh' except AssertionError as e: - self.assert_('A setup function was called' in str(e)) + self.assert_true('A setup function was called' in str(e)) else: self.fail('Expected exception') @@ -983,7 +983,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def working(): return 'Meh' self.assert_equal(app.test_client().get('/foo').data, b'Meh') - self.assert_(app.got_first_request) + self.assert_true(app.got_first_request) def test_before_first_request_functions(self): got = [] @@ -996,7 +996,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(got, [42]) c.get('/') self.assert_equal(got, [42]) - self.assert_(app.got_first_request) + self.assert_true(app.got_first_request) def test_routing_redirect_debugging(self): app = flask.Flask(__name__) @@ -1008,8 +1008,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.post('/foo', data={}) except AssertionError as e: - self.assert_('http://localhost/foo/' in str(e)) - self.assert_('Make sure to directly send your POST-request ' + self.assert_true('http://localhost/foo/' in str(e)) + self.assert_true('Make sure to directly send your POST-request ' 'to this URL' in str(e)) else: self.fail('Expected exception') @@ -1061,12 +1061,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): with self.assert_raises(ZeroDivisionError): c.get('/fail') - self.assert_(flask._request_ctx_stack.top is not None) - self.assert_(flask._app_ctx_stack.top is not None) + self.assert_true(flask._request_ctx_stack.top is not None) + self.assert_true(flask._app_ctx_stack.top is not None) # implicit appctx disappears too flask._request_ctx_stack.top.pop() - self.assert_(flask._request_ctx_stack.top is None) - self.assert_(flask._app_ctx_stack.top is None) + self.assert_true(flask._request_ctx_stack.top is None) + self.assert_true(flask._app_ctx_stack.top is None) class SubdomainTestCase(FlaskTestCase): diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index ed3ddbb2..088c72d2 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -185,7 +185,7 @@ class ModuleTestCase(FlaskTestCase): except TemplateNotFound as e: self.assert_equal(e.name, 'missing.html') else: - self.assert_(0, 'expected exception') + self.assert_true(0, 'expected exception') with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') @@ -201,13 +201,13 @@ class ModuleTestCase(FlaskTestCase): except NotFound: pass else: - self.assert_(0, 'expected exception') + self.assert_true(0, 'expected exception') try: f('../__init__.py') except NotFound: pass else: - self.assert_(0, 'expected exception') + self.assert_true(0, 'expected exception') # testcase for a security issue that may exist on windows systems import os @@ -220,7 +220,7 @@ class ModuleTestCase(FlaskTestCase): except NotFound: pass else: - self.assert_(0, 'expected exception') + self.assert_true(0, 'expected exception') finally: os.path = old_path @@ -380,7 +380,7 @@ class BlueprintTestCase(FlaskTestCase): except TemplateNotFound as e: self.assert_equal(e.name, 'missing.html') else: - self.assert_(0, 'expected exception') + self.assert_true(0, 'expected exception') with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') @@ -547,7 +547,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_true('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -558,7 +558,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_filter(my_reverse) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_true('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -569,7 +569,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_true('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -580,7 +580,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_filter(my_reverse, 'strrev') app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_true('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -656,9 +656,9 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('is_boolean' in app.jinja_env.tests.keys()) + self.assert_true('is_boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['is_boolean'], is_boolean) - self.assert_(app.jinja_env.tests['is_boolean'](False)) + self.assert_true(app.jinja_env.tests['is_boolean'](False)) def test_add_template_test(self): bp = flask.Blueprint('bp', __name__) @@ -667,9 +667,9 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_test(is_boolean) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('is_boolean' in app.jinja_env.tests.keys()) + self.assert_true('is_boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['is_boolean'], is_boolean) - self.assert_(app.jinja_env.tests['is_boolean'](False)) + self.assert_true(app.jinja_env.tests['is_boolean'](False)) def test_template_test_with_name(self): bp = flask.Blueprint('bp', __name__) @@ -678,9 +678,9 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_true('boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) - self.assert_(app.jinja_env.tests['boolean'](False)) + self.assert_true(app.jinja_env.tests['boolean'](False)) def test_add_template_test_with_name(self): bp = flask.Blueprint('bp', __name__) @@ -689,9 +689,9 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_test(is_boolean, 'boolean') app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_true('boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) - self.assert_(app.jinja_env.tests['boolean'](False)) + self.assert_true(app.jinja_env.tests['boolean'](False)) def test_template_test_with_template(self): bp = flask.Blueprint('bp', __name__) @@ -704,7 +704,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_template_test_after_route_with_template(self): app = flask.Flask(__name__) @@ -717,7 +717,7 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app.register_blueprint(bp, url_prefix='/py') rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_add_template_test_with_template(self): bp = flask.Blueprint('bp', __name__) @@ -730,7 +730,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_template_test_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -743,7 +743,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_add_template_test_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -756,7 +756,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 415d8aca..6633c6ba 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -28,7 +28,7 @@ class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): self.assert_equal(app.secret_key, 'devkey') self.assert_equal(app.config['TEST_KEY'], 'foo') - self.assert_('ConfigTestCase' not in app.config) + self.assert_true('ConfigTestCase' not in app.config) def test_config_from_file(self): app = flask.Flask(__name__) @@ -57,13 +57,13 @@ class ConfigTestCase(FlaskTestCase): try: app.config.from_envvar('FOO_SETTINGS') except RuntimeError as e: - self.assert_("'FOO_SETTINGS' is not set" in str(e)) + self.assert_true("'FOO_SETTINGS' is not set" in str(e)) else: - self.assert_(0, 'expected exception') - self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) + self.assert_true(0, 'expected exception') + self.assert_true(not app.config.from_envvar('FOO_SETTINGS', silent=True)) os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'} - self.assert_(app.config.from_envvar('FOO_SETTINGS')) + self.assert_true(app.config.from_envvar('FOO_SETTINGS')) self.common_object_test(app) finally: os.environ = env @@ -77,9 +77,9 @@ class ConfigTestCase(FlaskTestCase): app.config.from_envvar('FOO_SETTINGS') except IOError as e: msg = str(e) - self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' + self.assert_true(msg.startswith('[Errno 2] Unable to load configuration ' 'file (No such file or directory):')) - self.assert_(msg.endswith("missing.cfg'")) + self.assert_true(msg.endswith("missing.cfg'")) else: self.fail('expected IOError') self.assertFalse(app.config.from_envvar('FOO_SETTINGS', silent=True)) @@ -92,12 +92,12 @@ class ConfigTestCase(FlaskTestCase): app.config.from_pyfile('missing.cfg') except IOError as e: msg = str(e) - self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' + self.assert_true(msg.startswith('[Errno 2] Unable to load configuration ' 'file (No such file or directory):')) - self.assert_(msg.endswith("missing.cfg'")) + self.assert_true(msg.endswith("missing.cfg'")) else: - self.assert_(0, 'expected config') - self.assert_(not app.config.from_pyfile('missing.cfg', silent=True)) + self.assert_true(0, 'expected config') + self.assert_true(not app.config.from_pyfile('missing.cfg', silent=True)) def test_session_lifetime(self): app = flask.Flask(__name__) @@ -141,7 +141,7 @@ class InstanceTestCase(FlaskTestCase): try: flask.Flask(__name__, instance_path='instance') except ValueError as e: - self.assert_('must be absolute' in str(e)) + self.assert_true('must be absolute' in str(e)) else: self.fail('Expected value error') diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 147e23b0..a6d1be9d 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -42,7 +42,7 @@ class ExtImportHookTestCase(FlaskTestCase): def teardown(self): from flask import ext for key in ext.__dict__: - self.assert_('.' not in key) + self.assert_true('.' not in key) def test_flaskext_new_simple_import_normal(self): from flask.ext.newext_simple import ext_id @@ -108,12 +108,12 @@ class ExtImportHookTestCase(FlaskTestCase): import flask.ext.broken except ImportError: exc_type, exc_value, tb = sys.exc_info() - self.assert_(exc_type is ImportError) + self.assert_true(exc_type is ImportError) self.assert_equal(str(exc_value), 'No module named missing_module') - self.assert_(tb.tb_frame.f_globals is globals()) + self.assert_true(tb.tb_frame.f_globals is globals()) next = tb.tb_next - self.assert_('flask_broken/__init__.py' in next.tb_frame.f_code.co_filename) + self.assert_true('flask_broken/__init__.py' in next.tb_frame.f_code.co_filename) def suite(): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 9ca9eec3..702ab517 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -48,8 +48,8 @@ class JSONTestCase(FlaskTestCase): rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) self.assert_equal(rv.mimetype, 'application/json') - self.assert_('description' in flask.json.loads(rv.data)) - self.assert_('

' not in flask.json.loads(rv.data)['description']) + self.assert_true('description' in flask.json.loads(rv.data)) + self.assert_true('

' not in flask.json.loads(rv.data)['description']) def test_json_body_encoding(self): app = flask.Flask(__name__) @@ -167,7 +167,7 @@ class SendfileTestCase(FlaskTestCase): app = flask.Flask(__name__) with app.test_request_context(): rv = flask.send_file('static/index.html') - self.assert_(rv.direct_passthrough) + self.assert_true(rv.direct_passthrough) self.assert_equal(rv.mimetype, 'text/html') with app.open_resource('static/index.html') as f: self.assert_equal(rv.data, f.read()) @@ -177,8 +177,8 @@ class SendfileTestCase(FlaskTestCase): app.use_x_sendfile = True with app.test_request_context(): rv = flask.send_file('static/index.html') - self.assert_(rv.direct_passthrough) - self.assert_('x-sendfile' in rv.headers) + self.assert_true(rv.direct_passthrough) + self.assert_true('x-sendfile' in rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) self.assert_equal(rv.mimetype, 'text/html') @@ -201,7 +201,7 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) self.assert_equal(rv.mimetype, 'text/html') - self.assert_('x-sendfile' in rv.headers) + self.assert_true('x-sendfile' in rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) # mimetypes + etag @@ -229,7 +229,7 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): f = StringIO('Test') rv = flask.send_file(f) - self.assert_('x-sendfile' not in rv.headers) + self.assert_true('x-sendfile' not in rv.headers) # etags self.assert_equal(len(captured), 1) @@ -302,10 +302,10 @@ class LoggingTestCase(FlaskTestCase): def test_logger_cache(self): app = flask.Flask(__name__) logger1 = app.logger - self.assert_(app.logger is logger1) + self.assert_true(app.logger is logger1) self.assert_equal(logger1.name, __name__) app.logger_name = __name__ + '/test_logger_cache' - self.assert_(app.logger is not logger1) + self.assert_true(app.logger is not logger1) def test_debug_log(self): app = flask.Flask(__name__) @@ -325,10 +325,10 @@ class LoggingTestCase(FlaskTestCase): with catch_stderr() as err: c.get('/') out = err.getvalue() - self.assert_('WARNING in helpers [' in out) - self.assert_(os.path.basename(__file__.rsplit('.', 1)[0] + '.py') in out) - self.assert_('the standard library is dead' in out) - self.assert_('this is a debug statement' in out) + self.assert_true('WARNING in helpers [' in out) + self.assert_true(os.path.basename(__file__.rsplit('.', 1)[0] + '.py') in out) + self.assert_true('the standard library is dead' in out) + self.assert_true('this is a debug statement' in out) with catch_stderr() as err: try: @@ -336,7 +336,7 @@ class LoggingTestCase(FlaskTestCase): except ZeroDivisionError: pass else: - self.assert_(False, 'debug log ate the exception') + self.assert_true(False, 'debug log ate the exception') def test_debug_log_override(self): app = flask.Flask(__name__) @@ -357,13 +357,13 @@ class LoggingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_(b'Internal Server Error' in rv.data) + self.assert_true(b'Internal Server Error' in rv.data) err = out.getvalue() - self.assert_('Exception on / [GET]' in err) - self.assert_('Traceback (most recent call last):' in err) - self.assert_('1/0' in err) - self.assert_('ZeroDivisionError:' in err) + self.assert_true('Exception on / [GET]' in err) + self.assert_true('Traceback (most recent call last):' in err) + self.assert_true('1/0' in err) + self.assert_true('ZeroDivisionError:' in err) def test_processor_exceptions(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/reqctx.py b/flask/testsuite/reqctx.py index 6e379448..69b43f84 100644 --- a/flask/testsuite/reqctx.py +++ b/flask/testsuite/reqctx.py @@ -57,7 +57,7 @@ class RequestContextTestCase(FlaskTestCase): with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): pass except Exception as e: - self.assert_(isinstance(e, ValueError)) + self.assert_true(isinstance(e, ValueError)) self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:5000') does not match the " + \ "server name from the WSGI environment ('localhost')") @@ -93,17 +93,17 @@ class RequestContextTestCase(FlaskTestCase): self.assert_equal(index(), 'Hello World!') with app.test_request_context('/meh'): self.assert_equal(meh(), 'http://localhost/meh') - self.assert_(flask._request_ctx_stack.top is None) + self.assert_true(flask._request_ctx_stack.top is None) def test_context_test(self): app = flask.Flask(__name__) - self.assert_(not flask.request) - self.assert_(not flask.has_request_context()) + self.assert_true(not flask.request) + self.assert_true(not flask.has_request_context()) ctx = app.test_request_context() ctx.push() try: - self.assert_(flask.request) - self.assert_(flask.has_request_context()) + self.assert_true(flask.request) + self.assert_true(flask.has_request_context()) finally: ctx.pop() @@ -122,7 +122,7 @@ class RequestContextTestCase(FlaskTestCase): except RuntimeError: pass else: - self.assert_(0, 'expected runtime error') + self.assert_true(0, 'expected runtime error') def test_greenlet_context_copying(self): app = flask.Flask(__name__) @@ -132,14 +132,14 @@ class RequestContextTestCase(FlaskTestCase): def index(): reqctx = flask._request_ctx_stack.top.copy() def g(): - self.assert_(not flask.request) - self.assert_(not flask.current_app) + self.assert_true(not flask.request) + self.assert_true(not flask.current_app) with reqctx: - self.assert_(flask.request) + self.assert_true(flask.request) self.assert_equal(flask.current_app, app) self.assert_equal(flask.request.path, '/') self.assert_equal(flask.request.args['foo'], 'bar') - self.assert_(not flask.request) + self.assert_true(not flask.request) return 42 greenlets.append(greenlet(g)) return 'Hello World!' @@ -159,7 +159,7 @@ class RequestContextTestCase(FlaskTestCase): reqctx = flask._request_ctx_stack.top.copy() @flask.copy_current_request_context def g(): - self.assert_(flask.request) + self.assert_true(flask.request) self.assert_equal(flask.current_app, app) self.assert_equal(flask.request.path, '/') self.assert_equal(flask.request.args['foo'], 'bar') diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index 843c2f83..ffd575c1 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -92,7 +92,7 @@ class SignalsTestCase(FlaskTestCase): try: self.assert_equal(app.test_client().get('/').status_code, 500) self.assert_equal(len(recorded), 1) - self.assert_(isinstance(recorded[0], ZeroDivisionError)) + self.assert_true(isinstance(recorded[0], ZeroDivisionError)) finally: flask.got_request_exception.disconnect(record, app) diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py index e26b9085..a3cf5d0e 100644 --- a/flask/testsuite/subclassing.py +++ b/flask/testsuite/subclassing.py @@ -34,7 +34,7 @@ class FlaskSubclassingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_(b'Internal Server Error' in rv.data) + self.assert_true(b'Internal Server Error' in rv.data) err = out.getvalue() self.assert_equal(err, '') diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 0d3a2a5c..08a54d06 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -99,7 +99,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter() def my_reverse(s): return s[::-1] - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_true('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -108,7 +108,7 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] app.add_template_filter(my_reverse) - self.assert_('my_reverse' in app.jinja_env.filters.keys()) + self.assert_true('my_reverse' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -117,7 +117,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter('strrev') def my_reverse(s): return s[::-1] - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_true('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -126,7 +126,7 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] app.add_template_filter(my_reverse, 'strrev') - self.assert_('strrev' in app.jinja_env.filters.keys()) + self.assert_true('strrev' in app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -179,36 +179,36 @@ class TemplatingTestCase(FlaskTestCase): @app.template_test() def boolean(value): return isinstance(value, bool) - self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_true('boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], boolean) - self.assert_(app.jinja_env.tests['boolean'](False)) + self.assert_true(app.jinja_env.tests['boolean'](False)) def test_add_template_test(self): app = flask.Flask(__name__) def boolean(value): return isinstance(value, bool) app.add_template_test(boolean) - self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_true('boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], boolean) - self.assert_(app.jinja_env.tests['boolean'](False)) + self.assert_true(app.jinja_env.tests['boolean'](False)) def test_template_test_with_name(self): app = flask.Flask(__name__) @app.template_test('boolean') def is_boolean(value): return isinstance(value, bool) - self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_true('boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) - self.assert_(app.jinja_env.tests['boolean'](False)) + self.assert_true(app.jinja_env.tests['boolean'](False)) def test_add_template_test_with_name(self): app = flask.Flask(__name__) def is_boolean(value): return isinstance(value, bool) app.add_template_test(is_boolean, 'boolean') - self.assert_('boolean' in app.jinja_env.tests.keys()) + self.assert_true('boolean' in app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) - self.assert_(app.jinja_env.tests['boolean'](False)) + self.assert_true(app.jinja_env.tests['boolean'](False)) def test_template_test_with_template(self): app = flask.Flask(__name__) @@ -219,7 +219,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_add_template_test_with_template(self): app = flask.Flask(__name__) @@ -230,7 +230,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_template_test_with_name_and_template(self): app = flask.Flask(__name__) @@ -241,7 +241,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_add_template_test_with_name_and_template(self): app = flask.Flask(__name__) @@ -252,16 +252,16 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_(b'Success!' in rv.data) + self.assert_true(b'Success!' in rv.data) def test_add_template_global(self): app = flask.Flask(__name__) @app.template_global() def get_stuff(): return 42 - self.assert_('get_stuff' in app.jinja_env.globals.keys()) + self.assert_true('get_stuff' in app.jinja_env.globals.keys()) self.assert_equal(app.jinja_env.globals['get_stuff'], get_stuff) - self.assert_(app.jinja_env.globals['get_stuff'](), 42) + self.assert_true(app.jinja_env.globals['get_stuff'](), 42) with app.app_context(): rv = flask.render_template_string('{{ get_stuff() }}') self.assert_equal(rv, '42') diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 3379a463..a18db673 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -106,7 +106,7 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction() as sess: pass except RuntimeError as e: - self.assert_('Session backend did not open a session' in str(e)) + self.assert_true('Session backend did not open a session' in str(e)) else: self.fail('Expected runtime error') @@ -118,9 +118,9 @@ class TestToolsTestCase(FlaskTestCase): with app.test_client() as c: rv = c.get('/') req = flask.request._get_current_object() - self.assert_(req is not None) + self.assert_true(req is not None) with c.session_transaction(): - self.assert_(req is flask.request._get_current_object()) + self.assert_true(req is flask.request._get_current_object()) def test_session_transaction_needs_cookies(self): app = flask.Flask(__name__) @@ -130,7 +130,7 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction() as s: pass except RuntimeError as e: - self.assert_('cookies' in str(e)) + self.assert_true('cookies' in str(e)) else: self.fail('Expected runtime error') @@ -152,8 +152,8 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(resp.status_code, 200) resp = c.get('/other') - self.assert_(not hasattr(flask.g, 'value')) - self.assert_(b'Internal Server Error' in resp.data) + self.assert_true(not hasattr(flask.g, 'value')) + self.assert_true(b'Internal Server Error' in resp.data) self.assert_equal(resp.status_code, 500) flask.g.value = 23 From 8e9f0bdedca3edd5ba2e8902f05406acff8b8b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 17:30:37 +0200 Subject: [PATCH 0348/2940] Use assert_equal instead of assertEquals assertEquals is deprecated and its use inconsistent --- flask/testsuite/testing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index a18db673..2e735c47 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -219,8 +219,8 @@ class SubdomainTestCase(FlaskTestCase): url = flask.url_for('view', company_id='xxx') response = self.client.get(url) - self.assertEquals(200, response.status_code) - self.assertEquals(b'xxx', response.data) + self.assert_equal(200, response.status_code) + self.assert_equal(b'xxx', response.data) def test_nosubdomain(self): @@ -231,8 +231,8 @@ class SubdomainTestCase(FlaskTestCase): url = flask.url_for('view', company_id='xxx') response = self.client.get(url) - self.assertEquals(200, response.status_code) - self.assertEquals(b'xxx', response.data) + self.assert_equal(200, response.status_code) + self.assert_equal(b'xxx', response.data) def suite(): From 4d73ef1a194a4280d437db5cbc9ceb8850e99e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 19:19:46 +0200 Subject: [PATCH 0349/2940] Add missing msg argument to assert_true() --- flask/testsuite/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 2a869173..9088fc52 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -142,8 +142,8 @@ class FlaskTestCase(unittest.TestCase): with catcher: callable(*args, **kwargs) - def assert_true(self, x): - self.assertTrue(x) + def assert_true(self, x, msg=None): + self.assertTrue(x, msg) class _ExceptionCatcher(object): From 9f8a2075c79fec1b5481f5a5a95bb23b40bf68f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 20:12:50 +0200 Subject: [PATCH 0350/2940] Use assert_in where appropriate --- flask/testsuite/__init__.py | 8 ++++- flask/testsuite/basic.py | 54 +++++++++++++++++----------------- flask/testsuite/blueprints.py | 26 ++++++++-------- flask/testsuite/config.py | 4 +-- flask/testsuite/ext.py | 4 +-- flask/testsuite/helpers.py | 28 +++++++++--------- flask/testsuite/subclassing.py | 2 +- flask/testsuite/templating.py | 26 ++++++++-------- flask/testsuite/testing.py | 6 ++-- 9 files changed, 82 insertions(+), 76 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 9088fc52..72de1963 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -103,7 +103,7 @@ def emits_module_deprecation_warning(f): f(self, *args, **kwargs) self.assert_true(log, 'expected deprecation warning') for entry in log: - self.assert_true('Modules are deprecated' in str(entry['message'])) + self.assert_in('Modules are deprecated', str(entry['message'])) return update_wrapper(new_f, f) @@ -145,6 +145,12 @@ class FlaskTestCase(unittest.TestCase): def assert_true(self, x, msg=None): self.assertTrue(x, msg) + def assert_in(self, x, y): + self.assertIn(x, y) + + def assert_not_in(self, x, y): + self.assertNotIn(x, y) + class _ExceptionCatcher(object): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 20c2f0cc..0abcac10 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -173,8 +173,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com/') - self.assert_true('domain=.example.com' in rv.headers['set-cookie'].lower()) - self.assert_true('httponly' in rv.headers['set-cookie'].lower()) + self.assert_in('domain=.example.com', rv.headers['set-cookie'].lower()) + self.assert_in('httponly', rv.headers['set-cookie'].lower()) def test_session_using_server_name_and_port(self): app = flask.Flask(__name__) @@ -187,8 +187,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - self.assert_true('domain=.example.com' in rv.headers['set-cookie'].lower()) - self.assert_true('httponly' in rv.headers['set-cookie'].lower()) + self.assert_in('domain=.example.com', rv.headers['set-cookie'].lower()) + self.assert_in('httponly', rv.headers['set-cookie'].lower()) def test_session_using_server_name_port_and_path(self): app = flask.Flask(__name__) @@ -202,9 +202,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/foo') - self.assert_true('domain=example.com' in rv.headers['set-cookie'].lower()) - self.assert_true('path=/foo' in rv.headers['set-cookie'].lower()) - self.assert_true('httponly' in rv.headers['set-cookie'].lower()) + self.assert_in('domain=example.com', rv.headers['set-cookie'].lower()) + self.assert_in('path=/foo', rv.headers['set-cookie'].lower()) + self.assert_in('httponly', rv.headers['set-cookie'].lower()) def test_session_using_application_root(self): class PrefixPathMiddleware(object): @@ -226,7 +226,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - self.assert_true('path=/bar' in rv.headers['set-cookie'].lower()) + self.assert_in('path=/bar', rv.headers['set-cookie'].lower()) def test_session_using_session_settings(self): app = flask.Flask(__name__) @@ -245,10 +245,10 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'Hello World' rv = app.test_client().get('/', 'http://www.example.com:8080/test/') cookie = rv.headers['set-cookie'].lower() - self.assert_true('domain=.example.com' in cookie) - self.assert_true('path=/;' in cookie) - self.assert_true('secure' in cookie) - self.assert_true('httponly' not in cookie) + self.assert_in('domain=.example.com', cookie) + self.assert_in('path=/;', cookie) + self.assert_in('secure', cookie) + self.assert_not_in('httponly', cookie) def test_missing_session(self): app = flask.Flask(__name__) @@ -280,7 +280,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): client = app.test_client() rv = client.get('/') - self.assert_true('set-cookie' in rv.headers) + self.assert_in('set-cookie', rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime @@ -293,7 +293,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): permanent = False rv = app.test_client().get('/') - self.assert_true('set-cookie' in rv.headers) + self.assert_in('set-cookie', rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) self.assert_true(match is None) @@ -448,12 +448,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): return response @app.route('/') def index(): - self.assert_true('before' in evts) - self.assert_true('after' not in evts) + self.assert_in('before', evts) + self.assert_not_in('after', evts) return 'request' - self.assert_true('after' not in evts) + self.assert_not_in('after', evts) rv = app.test_client().get('/').data - self.assert_true(b'after' in evts) + self.assert_in(b'after', evts) self.assert_equal(rv, b'request|after') def test_after_request_processing(self): @@ -483,7 +483,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - self.assert_true(b'Response' in rv.data) + self.assert_in(b'Response', rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): @@ -499,7 +499,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - self.assert_true(b'Response' in rv.data) + self.assert_in(b'Response', rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): @@ -532,7 +532,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_true(b'Internal Server Error' in rv.data) + self.assert_in(b'Internal Server Error', rv.data) self.assert_equal(len(called), 2) def test_before_after_request_order(self): @@ -664,8 +664,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.post('/fail', data={'foo': 'index.txt'}) except DebugFilesKeyError as e: - self.assert_true('no file contents were transmitted' in str(e)) - self.assert_true('This was submitted: "index.txt"' in str(e)) + self.assert_in('no file contents were transmitted', str(e)) + self.assert_in('This was submitted: "index.txt"', str(e)) else: self.fail('Expected exception') @@ -974,7 +974,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def broken(): return 'Meh' except AssertionError as e: - self.assert_true('A setup function was called' in str(e)) + self.assert_in('A setup function was called', str(e)) else: self.fail('Expected exception') @@ -1008,9 +1008,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.post('/foo', data={}) except AssertionError as e: - self.assert_true('http://localhost/foo/' in str(e)) - self.assert_true('Make sure to directly send your POST-request ' - 'to this URL' in str(e)) + self.assert_in('http://localhost/foo/', str(e)) + self.assert_in('Make sure to directly send your POST-request ' + 'to this URL', str(e)) else: self.fail('Expected exception') diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 088c72d2..89a8f2d7 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -547,7 +547,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('my_reverse' in app.jinja_env.filters.keys()) + self.assert_in('my_reverse', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -558,7 +558,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_filter(my_reverse) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('my_reverse' in app.jinja_env.filters.keys()) + self.assert_in('my_reverse', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -569,7 +569,7 @@ class BlueprintTestCase(FlaskTestCase): return s[::-1] app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('strrev' in app.jinja_env.filters.keys()) + self.assert_in('strrev', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -580,7 +580,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_filter(my_reverse, 'strrev') app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('strrev' in app.jinja_env.filters.keys()) + self.assert_in('strrev', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -656,7 +656,7 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('is_boolean' in app.jinja_env.tests.keys()) + self.assert_in('is_boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['is_boolean'], is_boolean) self.assert_true(app.jinja_env.tests['is_boolean'](False)) @@ -667,7 +667,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_test(is_boolean) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('is_boolean' in app.jinja_env.tests.keys()) + self.assert_in('is_boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['is_boolean'], is_boolean) self.assert_true(app.jinja_env.tests['is_boolean'](False)) @@ -678,7 +678,7 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('boolean' in app.jinja_env.tests.keys()) + self.assert_in('boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) self.assert_true(app.jinja_env.tests['boolean'](False)) @@ -689,7 +689,7 @@ class BlueprintTestCase(FlaskTestCase): bp.add_app_template_test(is_boolean, 'boolean') app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/py') - self.assert_true('boolean' in app.jinja_env.tests.keys()) + self.assert_in('boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) self.assert_true(app.jinja_env.tests['boolean'](False)) @@ -704,7 +704,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_template_test_after_route_with_template(self): app = flask.Flask(__name__) @@ -717,7 +717,7 @@ class BlueprintTestCase(FlaskTestCase): return isinstance(value, bool) app.register_blueprint(bp, url_prefix='/py') rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_add_template_test_with_template(self): bp = flask.Blueprint('bp', __name__) @@ -730,7 +730,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_template_test_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -743,7 +743,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_add_template_test_with_name_and_template(self): bp = flask.Blueprint('bp', __name__) @@ -756,7 +756,7 @@ class BlueprintTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 6633c6ba..8aaf5285 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -28,7 +28,7 @@ class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): self.assert_equal(app.secret_key, 'devkey') self.assert_equal(app.config['TEST_KEY'], 'foo') - self.assert_true('ConfigTestCase' not in app.config) + self.assert_not_in('ConfigTestCase', app.config) def test_config_from_file(self): app = flask.Flask(__name__) @@ -141,7 +141,7 @@ class InstanceTestCase(FlaskTestCase): try: flask.Flask(__name__, instance_path='instance') except ValueError as e: - self.assert_true('must be absolute' in str(e)) + self.assert_in('must be absolute', str(e)) else: self.fail('Expected value error') diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index a6d1be9d..04226181 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -42,7 +42,7 @@ class ExtImportHookTestCase(FlaskTestCase): def teardown(self): from flask import ext for key in ext.__dict__: - self.assert_true('.' not in key) + self.assert_not_in('.', key) def test_flaskext_new_simple_import_normal(self): from flask.ext.newext_simple import ext_id @@ -113,7 +113,7 @@ class ExtImportHookTestCase(FlaskTestCase): self.assert_true(tb.tb_frame.f_globals is globals()) next = tb.tb_next - self.assert_true('flask_broken/__init__.py' in next.tb_frame.f_code.co_filename) + self.assert_in('flask_broken/__init__.py', next.tb_frame.f_code.co_filename) def suite(): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 702ab517..b76d36d1 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -48,8 +48,8 @@ class JSONTestCase(FlaskTestCase): rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) self.assert_equal(rv.mimetype, 'application/json') - self.assert_true('description' in flask.json.loads(rv.data)) - self.assert_true('

' not in flask.json.loads(rv.data)['description']) + self.assert_in('description', flask.json.loads(rv.data)) + self.assert_not_in('

', flask.json.loads(rv.data)['description']) def test_json_body_encoding(self): app = flask.Flask(__name__) @@ -178,7 +178,7 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): rv = flask.send_file('static/index.html') self.assert_true(rv.direct_passthrough) - self.assert_true('x-sendfile' in rv.headers) + self.assert_in('x-sendfile', rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) self.assert_equal(rv.mimetype, 'text/html') @@ -201,7 +201,7 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) self.assert_equal(rv.mimetype, 'text/html') - self.assert_true('x-sendfile' in rv.headers) + self.assert_in('x-sendfile', rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) # mimetypes + etag @@ -229,7 +229,7 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): f = StringIO('Test') rv = flask.send_file(f) - self.assert_true('x-sendfile' not in rv.headers) + self.assert_not_in('x-sendfile', rv.headers) # etags self.assert_equal(len(captured), 1) @@ -325,10 +325,10 @@ class LoggingTestCase(FlaskTestCase): with catch_stderr() as err: c.get('/') out = err.getvalue() - self.assert_true('WARNING in helpers [' in out) - self.assert_true(os.path.basename(__file__.rsplit('.', 1)[0] + '.py') in out) - self.assert_true('the standard library is dead' in out) - self.assert_true('this is a debug statement' in out) + self.assert_in('WARNING in helpers [', out) + self.assert_in(os.path.basename(__file__.rsplit('.', 1)[0] + '.py'), out) + self.assert_in('the standard library is dead', out) + self.assert_in('this is a debug statement', out) with catch_stderr() as err: try: @@ -357,13 +357,13 @@ class LoggingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_true(b'Internal Server Error' in rv.data) + self.assert_in(b'Internal Server Error', rv.data) err = out.getvalue() - self.assert_true('Exception on / [GET]' in err) - self.assert_true('Traceback (most recent call last):' in err) - self.assert_true('1/0' in err) - self.assert_true('ZeroDivisionError:' in err) + self.assert_in('Exception on / [GET]', err) + self.assert_in('Traceback (most recent call last):', err) + self.assert_in('1/0', err) + self.assert_in('ZeroDivisionError:', err) def test_processor_exceptions(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py index a3cf5d0e..dbfdd499 100644 --- a/flask/testsuite/subclassing.py +++ b/flask/testsuite/subclassing.py @@ -34,7 +34,7 @@ class FlaskSubclassingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - self.assert_true(b'Internal Server Error' in rv.data) + self.assert_in(b'Internal Server Error', rv.data) err = out.getvalue() self.assert_equal(err, '') diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 08a54d06..b2870dea 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -99,7 +99,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter() def my_reverse(s): return s[::-1] - self.assert_true('my_reverse' in app.jinja_env.filters.keys()) + self.assert_in('my_reverse', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -108,7 +108,7 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] app.add_template_filter(my_reverse) - self.assert_true('my_reverse' in app.jinja_env.filters.keys()) + self.assert_in('my_reverse', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse) self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba') @@ -117,7 +117,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter('strrev') def my_reverse(s): return s[::-1] - self.assert_true('strrev' in app.jinja_env.filters.keys()) + self.assert_in('strrev', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -126,7 +126,7 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] app.add_template_filter(my_reverse, 'strrev') - self.assert_true('strrev' in app.jinja_env.filters.keys()) + self.assert_in('strrev', app.jinja_env.filters.keys()) self.assert_equal(app.jinja_env.filters['strrev'], my_reverse) self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba') @@ -179,7 +179,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_test() def boolean(value): return isinstance(value, bool) - self.assert_true('boolean' in app.jinja_env.tests.keys()) + self.assert_in('boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], boolean) self.assert_true(app.jinja_env.tests['boolean'](False)) @@ -188,7 +188,7 @@ class TemplatingTestCase(FlaskTestCase): def boolean(value): return isinstance(value, bool) app.add_template_test(boolean) - self.assert_true('boolean' in app.jinja_env.tests.keys()) + self.assert_in('boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], boolean) self.assert_true(app.jinja_env.tests['boolean'](False)) @@ -197,7 +197,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_test('boolean') def is_boolean(value): return isinstance(value, bool) - self.assert_true('boolean' in app.jinja_env.tests.keys()) + self.assert_in('boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) self.assert_true(app.jinja_env.tests['boolean'](False)) @@ -206,7 +206,7 @@ class TemplatingTestCase(FlaskTestCase): def is_boolean(value): return isinstance(value, bool) app.add_template_test(is_boolean, 'boolean') - self.assert_true('boolean' in app.jinja_env.tests.keys()) + self.assert_in('boolean', app.jinja_env.tests.keys()) self.assert_equal(app.jinja_env.tests['boolean'], is_boolean) self.assert_true(app.jinja_env.tests['boolean'](False)) @@ -219,7 +219,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_add_template_test_with_template(self): app = flask.Flask(__name__) @@ -230,7 +230,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_template_test_with_name_and_template(self): app = flask.Flask(__name__) @@ -241,7 +241,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_add_template_test_with_name_and_template(self): app = flask.Flask(__name__) @@ -252,14 +252,14 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_test.html', value=False) rv = app.test_client().get('/') - self.assert_true(b'Success!' in rv.data) + self.assert_in(b'Success!', rv.data) def test_add_template_global(self): app = flask.Flask(__name__) @app.template_global() def get_stuff(): return 42 - self.assert_true('get_stuff' in app.jinja_env.globals.keys()) + self.assert_in('get_stuff', app.jinja_env.globals.keys()) self.assert_equal(app.jinja_env.globals['get_stuff'], get_stuff) self.assert_true(app.jinja_env.globals['get_stuff'](), 42) with app.app_context(): diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 2e735c47..a60109e1 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -106,7 +106,7 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction() as sess: pass except RuntimeError as e: - self.assert_true('Session backend did not open a session' in str(e)) + self.assert_in('Session backend did not open a session', str(e)) else: self.fail('Expected runtime error') @@ -130,7 +130,7 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction() as s: pass except RuntimeError as e: - self.assert_true('cookies' in str(e)) + self.assert_in('cookies', str(e)) else: self.fail('Expected runtime error') @@ -153,7 +153,7 @@ class TestToolsTestCase(FlaskTestCase): resp = c.get('/other') self.assert_true(not hasattr(flask.g, 'value')) - self.assert_true(b'Internal Server Error' in resp.data) + self.assert_in(b'Internal Server Error', resp.data) self.assert_equal(resp.status_code, 500) flask.g.value = 23 From 62e7275bdf2bce19eb9fcfd31780e9314f96f092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 20:17:29 +0200 Subject: [PATCH 0351/2940] Use assert_false where appropriate --- flask/testsuite/__init__.py | 3 +++ flask/testsuite/basic.py | 8 ++++---- flask/testsuite/config.py | 4 ++-- flask/testsuite/reqctx.py | 10 +++++----- flask/testsuite/testing.py | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 72de1963..88cd4d88 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -145,6 +145,9 @@ class FlaskTestCase(unittest.TestCase): def assert_true(self, x, msg=None): self.assertTrue(x, msg) + def assert_false(self, x, msg=None): + self.assertFalse(x, msg) + def assert_in(self, x, y): self.assertIn(x, y) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 0abcac10..85b758b1 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -78,7 +78,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - self.assert_true(not rv.data) # head truncates + self.assert_false(rv.data) # head truncates self.assert_equal(c.post('/more').data, b'POST') self.assert_equal(c.get('/more').data, b'GET') rv = c.delete('/more') @@ -102,7 +102,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - self.assert_true(not rv.data) # head truncates + self.assert_false(rv.data) # head truncates self.assert_equal(c.post('/more').data, b'POST') self.assert_equal(c.get('/more').data, b'GET') rv = c.delete('/more') @@ -347,7 +347,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.secret_key = 'testkey' with app.test_request_context(): - self.assert_true(not flask.session.modified) + self.assert_false(flask.session.modified) flask.flash('Zap') flask.session.modified = False flask.flash('Zip') @@ -967,7 +967,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/') def index(): return 'Awesome' - self.assert_true(not app.got_first_request) + self.assert_false(app.got_first_request) self.assert_equal(app.test_client().get('/').data, b'Awesome') try: @app.route('/foo') diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 8aaf5285..477c6db9 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -60,7 +60,7 @@ class ConfigTestCase(FlaskTestCase): self.assert_true("'FOO_SETTINGS' is not set" in str(e)) else: self.assert_true(0, 'expected exception') - self.assert_true(not app.config.from_envvar('FOO_SETTINGS', silent=True)) + self.assert_false(app.config.from_envvar('FOO_SETTINGS', silent=True)) os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'} self.assert_true(app.config.from_envvar('FOO_SETTINGS')) @@ -97,7 +97,7 @@ class ConfigTestCase(FlaskTestCase): self.assert_true(msg.endswith("missing.cfg'")) else: self.assert_true(0, 'expected config') - self.assert_true(not app.config.from_pyfile('missing.cfg', silent=True)) + self.assert_false(app.config.from_pyfile('missing.cfg', silent=True)) def test_session_lifetime(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/reqctx.py b/flask/testsuite/reqctx.py index 69b43f84..c232a74c 100644 --- a/flask/testsuite/reqctx.py +++ b/flask/testsuite/reqctx.py @@ -97,8 +97,8 @@ class RequestContextTestCase(FlaskTestCase): def test_context_test(self): app = flask.Flask(__name__) - self.assert_true(not flask.request) - self.assert_true(not flask.has_request_context()) + self.assert_false(flask.request) + self.assert_false(flask.has_request_context()) ctx = app.test_request_context() ctx.push() try: @@ -132,14 +132,14 @@ class RequestContextTestCase(FlaskTestCase): def index(): reqctx = flask._request_ctx_stack.top.copy() def g(): - self.assert_true(not flask.request) - self.assert_true(not flask.current_app) + self.assert_false(flask.request) + self.assert_false(flask.current_app) with reqctx: self.assert_true(flask.request) self.assert_equal(flask.current_app, app) self.assert_equal(flask.request.path, '/') self.assert_equal(flask.request.args['foo'], 'bar') - self.assert_true(not flask.request) + self.assert_false(flask.request) return 42 greenlets.append(greenlet(g)) return 'Hello World!' diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index a60109e1..e7206d21 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -152,7 +152,7 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(resp.status_code, 200) resp = c.get('/other') - self.assert_true(not hasattr(flask.g, 'value')) + self.assert_false(hasattr(flask.g, 'value')) self.assert_in(b'Internal Server Error', resp.data) self.assert_equal(resp.status_code, 500) flask.g.value = 23 From 8f73c552a96cc54e6abefcfc0c4e9d4cdd7dc040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 20:53:32 +0200 Subject: [PATCH 0352/2940] Add missing assertIn, assertNotIn methods on 2.6 --- flask/testsuite/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 88cd4d88..89b8cec1 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -154,6 +154,13 @@ class FlaskTestCase(unittest.TestCase): def assert_not_in(self, x, y): self.assertNotIn(x, y) + if sys.version_info[:2] == (2, 6): + def assertIn(self, x, y): + assert x in y, "%r unexpectedly not in %r" % (x, y) + + def assertNotIn(self, x, y): + assert x not in y, "%r unexpectedly in %r" % (x, y) + class _ExceptionCatcher(object): From 3f80b0fd6c054d924c41a3c91152ec1984d8c28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 21:03:01 +0200 Subject: [PATCH 0353/2940] module name is quoted in ImportErrors on 3.x --- flask/testsuite/ext.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 04226181..f6209c67 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -12,6 +12,7 @@ import sys import unittest from flask.testsuite import FlaskTestCase +from flask._compat import PY2 from six.moves import reload_module class ExtImportHookTestCase(FlaskTestCase): @@ -109,7 +110,11 @@ class ExtImportHookTestCase(FlaskTestCase): except ImportError: exc_type, exc_value, tb = sys.exc_info() self.assert_true(exc_type is ImportError) - self.assert_equal(str(exc_value), 'No module named missing_module') + if PY2: + message = 'No module named missing_module' + else: + message = 'No module named \'missing_module\'' + self.assert_equal(str(exc_value), message) self.assert_true(tb.tb_frame.f_globals is globals()) next = tb.tb_next From 4bea6bbe6d2200dfdbcca3dfc2fe561488c82a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 21:07:53 +0200 Subject: [PATCH 0354/2940] Make DebugFilesKeyError.__str__ return str on 3.x --- flask/debughelpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 504fab93..f3bac185 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -8,6 +8,7 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from flask._compat import implements_to_string class UnexpectedUnicodeError(AssertionError, UnicodeError): @@ -16,6 +17,7 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError): """ +@implements_to_string class DebugFilesKeyError(KeyError, AssertionError): """Raised from request.files during debugging. The idea is that it can provide a better error message than just a generic KeyError/BadRequest. @@ -33,7 +35,7 @@ class DebugFilesKeyError(KeyError, AssertionError): buf.append('\n\nThe browser instead transmitted some file names. ' 'This was submitted: %s' % ', '.join('"%s"' % x for x in form_matches)) - self.msg = ''.join(buf).encode('utf-8') + self.msg = ''.join(buf) def __str__(self): return self.msg From 3d36d6efb9371be92f6f22c844ed6a17c87f4a88 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 22 May 2013 21:09:32 +0200 Subject: [PATCH 0355/2940] Fix leak in leak detection code If ensure_clean_request_context found a leak, it would raise an AssertionError and not clean up the leak, and therefore affect other testcases. --- flask/testsuite/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 88cd4d88..5f5935cd 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -116,7 +116,10 @@ class FlaskTestCase(unittest.TestCase): def ensure_clean_request_context(self): # make sure we're not leaking a request context since we are # testing flask internally in debug mode in a few cases - self.assert_equal(flask._request_ctx_stack.top, None) + leaks = [] + while flask._request_ctx_stack.top is not None: + leaks.append(flask._request_ctx_stack.pop()) + self.assert_equal(leaks, []) def setup(self): pass From 135c53a5f2f990512d2be348dc16ef719233a314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 21:30:45 +0200 Subject: [PATCH 0356/2940] Fix .iteritems() access in flask.sessions --- flask/sessions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 04b7f8af..0b35b1c2 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -15,9 +15,9 @@ from datetime import datetime from werkzeug.http import http_date, parse_date from werkzeug.datastructures import CallbackDict from . import Markup, json +from ._compat import iteritems, text_type from itsdangerous import URLSafeTimedSerializer, BadSignature -import six def total_seconds(td): @@ -63,16 +63,16 @@ class TaggedJSONSerializer(object): elif isinstance(value, uuid.UUID): return {' u': value.hex} elif callable(getattr(value, '__html__', None)): - return {' m': six.text_type(value.__html__())} + return {' m': text_type(value.__html__())} elif isinstance(value, list): return [_tag(x) for x in value] elif isinstance(value, datetime): return {' d': http_date(value)} elif isinstance(value, dict): - return dict((k, _tag(v)) for k, v in six.iteritems(value)) + return dict((k, _tag(v)) for k, v in iteritems(value)) elif isinstance(value, str): try: - return six.text_type(value) + return text_type(value) except UnicodeError: raise UnexpectedUnicodeError(u'A byte string with ' u'non-ASCII data was passed to the session system ' @@ -85,7 +85,7 @@ class TaggedJSONSerializer(object): def object_hook(obj): if len(obj) != 1: return obj - the_key, the_value = six.advance_iterator(obj.iteritems()) + the_key, the_value = next(iteritems(obj)) if the_key == ' t': return tuple(the_value) elif the_key == ' u': From a0801719f8f24a0e3192eca203bbd341c4e557ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 21:40:30 +0200 Subject: [PATCH 0357/2940] Remove six dependency --- flask/helpers.py | 3 +-- flask/json.py | 4 ++-- flask/templating.py | 6 +++--- flask/testsuite/basic.py | 4 ++-- flask/testsuite/blueprints.py | 4 ++-- flask/testsuite/ext.py | 5 ++++- flask/testsuite/helpers.py | 11 +++++------ flask/testsuite/testing.py | 4 ++-- setup.py | 1 - 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 1359bba6..dbbbf2e6 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -24,7 +24,6 @@ from functools import update_wrapper from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound -import six from flask._compat import string_types, text_type # this was moved in 0.7 @@ -128,7 +127,7 @@ def stream_with_context(generator_or_function): # pushed. This item is discarded. Then when the iteration continues the # real generator is executed. wrapped_g = generator() - six.advance_iterator(wrapped_g) + next(wrapped_g) return wrapped_g diff --git a/flask/json.py b/flask/json.py index 6b95fde0..7777ba5d 100644 --- a/flask/json.py +++ b/flask/json.py @@ -11,13 +11,13 @@ import uuid from datetime import datetime from .globals import current_app, request +from ._compat import text_type from werkzeug.http import http_date # Use the same json implementation as itsdangerous on which we # depend anyways. from itsdangerous import json as _json -import six # figure out if simplejson escapes slashes. This behavior was changed @@ -60,7 +60,7 @@ class JSONEncoder(_json.JSONEncoder): if isinstance(o, uuid.UUID): return str(o) if hasattr(o, '__html__'): - return six.text_type(o.__html__()) + return text_type(o.__html__()) return _json.JSONEncoder.default(self, o) diff --git a/flask/templating.py b/flask/templating.py index 754c6893..63adb092 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -15,7 +15,7 @@ from jinja2 import BaseLoader, Environment as BaseEnvironment, \ from .globals import _request_ctx_stack, _app_ctx_stack from .signals import template_rendered from .module import blueprint_is_module -import six +from ._compat import itervalues, iteritems def _default_template_ctx_processor(): @@ -80,7 +80,7 @@ class DispatchingJinjaLoader(BaseLoader): except (ValueError, KeyError): pass - for blueprint in six.itervalues(self.app.blueprints): + for blueprint in itervalues(self.app.blueprints): if blueprint_is_module(blueprint): continue loader = blueprint.jinja_loader @@ -93,7 +93,7 @@ class DispatchingJinjaLoader(BaseLoader): if loader is not None: result.update(loader.list_templates()) - for name, blueprint in six.iteritems(self.app.blueprints): + for name, blueprint in iteritems(self.app.blueprints): loader = blueprint.jinja_loader if loader is not None: for template in loader.list_templates(): diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 85b758b1..485ed5fd 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -17,10 +17,10 @@ import unittest from datetime import datetime from threading import Thread from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning +from flask._compat import text_type from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_date from werkzeug.routing import BuildError -import six class BasicFunctionalityTestCase(FlaskTestCase): @@ -276,7 +276,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/test') def test(): - return six.text_type(flask.session.permanent) + return text_type(flask.session.permanent) client = app.test_client() rv = client.get('/') diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 89a8f2d7..97a196d7 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -13,10 +13,10 @@ import flask import unittest import warnings from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning +from flask._compat import text_type from werkzeug.exceptions import NotFound from werkzeug.http import parse_cache_control_header from jinja2 import TemplateNotFound -import six # import moduleapp here because it uses deprecated features and we don't @@ -303,7 +303,7 @@ class BlueprintTestCase(FlaskTestCase): @bp.route('/bar') def bar(bar): - return six.text_type(bar) + return text_type(bar) app = flask.Flask(__name__) app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index f6209c67..370a31d8 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -11,9 +11,12 @@ import sys import unittest +try: + from imp import reload as reload_module +except ImportError: + reload_module = reload from flask.testsuite import FlaskTestCase from flask._compat import PY2 -from six.moves import reload_module class ExtImportHookTestCase(FlaskTestCase): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index b76d36d1..d0054de1 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -15,8 +15,7 @@ import unittest from logging import StreamHandler from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr from werkzeug.http import parse_cache_control_header, parse_options_header -import six -from flask._compat import StringIO +from flask._compat import StringIO, text_type def has_encoding(name): @@ -34,7 +33,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/json', methods=['POST']) def return_json(): - return six.text_type(flask.request.json) + return text_type(flask.request.json) c = app.test_client() rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) @@ -43,7 +42,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/json', methods=['POST']) def return_json(): - return six.text_type(flask.request.json) + return text_type(flask.request.json) c = app.test_client() rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) @@ -95,7 +94,7 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/add', methods=['POST']) def add(): - return six.text_type(flask.request.json['a'] + flask.request.json['b']) + return text_type(flask.request.json['a'] + flask.request.json['b']) c = app.test_client() rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), content_type='application/json') @@ -506,7 +505,7 @@ class StreamingTestCase(FlaskTestCase): def close(self): called.append(42) def next(self): - return six.advance_iterator(self._gen) + return next(self._gen) @app.route('/') def index(): def generate(): diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index e7206d21..cd96b497 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -12,7 +12,7 @@ import flask import unittest from flask.testsuite import FlaskTestCase -import six +from flask._compat import text_type class TestToolsTestCase(FlaskTestCase): @@ -84,7 +84,7 @@ class TestToolsTestCase(FlaskTestCase): @app.route('/') def index(): - return six.text_type(flask.session['foo']) + return text_type(flask.session['foo']) with app.test_client() as c: with c.session_transaction() as sess: diff --git a/setup.py b/setup.py index 1d3e36ad..ddc83251 100644 --- a/setup.py +++ b/setup.py @@ -91,7 +91,6 @@ setup( zip_safe=False, platforms='any', install_requires=[ - 'six>=1.3.0', 'Werkzeug>=0.7', 'Jinja2>=2.4', 'itsdangerous>=0.17' From 43b6d0a6d062ace75cc2b0f7200b770eac23edcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 22:49:25 +0200 Subject: [PATCH 0358/2940] Ensure that config file is closed immediately --- flask/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/config.py b/flask/config.py index ddb113a5..4d9ac23a 100644 --- a/flask/config.py +++ b/flask/config.py @@ -125,7 +125,8 @@ class Config(dict): d = imp.new_module('config') d.__file__ = filename try: - exec(compile(open(filename).read(), filename, 'exec'), d.__dict__) + with open(filename) as config_file: + exec(compile(config_file.read(), filename, 'exec'), d.__dict__) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): return False From 79ec3d81c1c0e4db7eb800e69f2c7aab3d8d02b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Wed, 22 May 2013 22:58:12 +0200 Subject: [PATCH 0359/2940] Prevent UnboundLocalError in test_build_error_handler --- flask/testsuite/basic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 485ed5fd..656233b8 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -753,8 +753,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: with app.test_request_context(): flask.url_for('spam') - except BuildError as error: - pass + except BuildError as err: + error = err try: raise RuntimeError('Test case where BuildError is not current.') except RuntimeError: From eb023bcfad7741248705f6715055ffe46928b7fe Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 May 2013 13:46:51 +0100 Subject: [PATCH 0360/2940] Support old and new name for json --- flask/json.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flask/json.py b/flask/json.py index 717eb2ab..0f1d4c2a 100644 --- a/flask/json.py +++ b/flask/json.py @@ -15,8 +15,11 @@ from .globals import current_app, request from werkzeug.http import http_date # Use the same json implementation as itsdangerous on which we -# depend anyways. -from itsdangerous import simplejson as _json +# depend anyways. This name changed at one point so support both. +try: + from itsdangerous import simplejson as _json +except ImportError: + from itsdangerous import json as _json # figure out if simplejson escapes slashes. This behavior was changed From 4c27f7a8c4bbe6621681178be716e6270067a3ad Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 May 2013 13:59:10 +0100 Subject: [PATCH 0361/2940] Removed incorrect JSON exception subclasses --- CHANGES | 3 +++ flask/exceptions.py | 48 -------------------------------------- flask/testsuite/helpers.py | 14 +---------- flask/wrappers.py | 19 ++++++--------- 4 files changed, 11 insertions(+), 73 deletions(-) delete mode 100644 flask/exceptions.py diff --git a/CHANGES b/CHANGES index e383b56f..46b816a5 100644 --- a/CHANGES +++ b/CHANGES @@ -54,6 +54,9 @@ Release date to be decided. - Added `message_flashed` signal that simplifies flashing testing. - Added support for copying of request contexts for better working with greenlets. +- Removed custom JSON HTTP exception subclasses. If you were relying on them + you can reintroduce them again yourself trivially. Using them however is + strongly discouraged as the interface was flawed. Version 0.9 ----------- diff --git a/flask/exceptions.py b/flask/exceptions.py deleted file mode 100644 index 83b9556b..00000000 --- a/flask/exceptions.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -""" - flask.exceptions - ~~~~~~~~~~~~ - - Flask specific additions to :class:`~werkzeug.exceptions.HTTPException` - - :copyright: (c) 2011 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -from werkzeug.exceptions import HTTPException, BadRequest -from . import json - - -class JSONHTTPException(HTTPException): - """A base class for HTTP exceptions with ``Content-Type: - application/json``. - - The ``description`` attribute of this class must set to a string (*not* an - HTML string) which describes the error. - - """ - - def get_body(self, environ): - """Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to - return the description of this error in JSON format instead of HTML. - - """ - return json.dumps(dict(description=self.get_description(environ))) - - def get_headers(self, environ): - """Returns a list of headers including ``Content-Type: - application/json``. - - """ - return [('Content-Type', 'application/json')] - - -class JSONBadRequest(JSONHTTPException, BadRequest): - """Represents an HTTP ``400 Bad Request`` error whose body contains an - error message in JSON format instead of HTML format (as in the superclass). - """ - - #: The description of the error which occurred as a string. - description = ( - 'The browser (or proxy) sent a request that this server could not ' - 'understand.' - ) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index fdf2d89f..8e471af9 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -35,23 +35,11 @@ class JSONTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/json', methods=['POST']) def return_json(): - return unicode(flask.request.json) + return flask.jsonify(foo=unicode(flask.request.json)) c = app.test_client() rv = c.post('/json', data='malformed', content_type='application/json') self.assert_equal(rv.status_code, 400) - def test_json_bad_requests_content_type(self): - app = flask.Flask(__name__) - @app.route('/json', methods=['POST']) - def return_json(): - return unicode(flask.request.json) - c = app.test_client() - rv = c.post('/json', data='malformed', content_type='application/json') - self.assert_equal(rv.status_code, 400) - self.assert_equal(rv.mimetype, 'application/json') - self.assert_('description' in flask.json.loads(rv.data)) - self.assert_('

' not in flask.json.loads(rv.data)['description']) - def test_json_body_encoding(self): app = flask.Flask(__name__) app.testing = True diff --git a/flask/wrappers.py b/flask/wrappers.py index a56fe5d7..100decd0 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -11,8 +11,8 @@ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase from werkzeug.utils import cached_property +from werkzeug.exceptions import BadRequest -from .exceptions import JSONBadRequest from .debughelpers import attach_enctype_error_multidict from . import json from .globals import _request_ctx_stack @@ -107,21 +107,16 @@ class Request(RequestBase): def on_json_loading_failed(self, e): """Called if decoding of the JSON data failed. The return value of this method is used by :attr:`json` when an error occurred. The default - implementation raises a :class:`JSONBadRequest`, which is a subclass of - :class:`~werkzeug.exceptions.BadRequest` which sets the - ``Content-Type`` to ``application/json`` and provides a JSON-formatted - error description:: + implementation just raises a :class:`BadRequest` exception. - {"description": "The browser (or proxy) sent a request that \ - this server could not understand."} - - .. versionchanged:: 0.9 - Return a :class:`JSONBadRequest` instead of a - :class:`~werkzeug.exceptions.BadRequest` by default. + .. versionchanged:: 0.10 + Removed buggy previous behavior of generating a random JSON + response. If you want that behavior back you can trivially + add it by subclassing. .. versionadded:: 0.8 """ - raise JSONBadRequest() + raise BadRequest() def _load_form_data(self): RequestBase._load_form_data(self) From 85ba8c96e9371368cff772e4e270cf685d015e03 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 May 2013 14:07:25 +0100 Subject: [PATCH 0362/2940] Fixed a broken test --- flask/testsuite/ext.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 370a31d8..1a7a4a5a 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -120,7 +120,8 @@ class ExtImportHookTestCase(FlaskTestCase): self.assert_equal(str(exc_value), message) self.assert_true(tb.tb_frame.f_globals is globals()) - next = tb.tb_next + # reraise() adds a second frame so we need to skip that one too. + next = tb.tb_next.tb_next self.assert_in('flask_broken/__init__.py', next.tb_frame.f_code.co_filename) From 22816b1d3d6db4dbcecf8c07c8e54a0ad2a8d28b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 May 2013 14:09:22 +0100 Subject: [PATCH 0363/2940] Removed 2.5 support in master --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd590bea..b0c10252 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,10 @@ language: python python: - - 2.5 - 2.6 - 2.7 - pypy -before_install: pip install simplejson - script: python setup.py test branches: From df5890ad715e35b58b7a00d202cb7145fee8418d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 May 2013 14:15:27 +0100 Subject: [PATCH 0364/2940] Change travis test command --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b0c10252..6594edd5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: - 2.7 - pypy -script: python setup.py test +script: make test branches: except: From 250247d96c84b3bb3e45af5b8c1411aca2b35287 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 May 2013 14:17:51 +0100 Subject: [PATCH 0365/2940] Added install section --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6594edd5..4929600e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,8 @@ python: - 2.7 - pypy +install: pip install --editable . + script: make test branches: From 12c08c03fb9e58d83094021a559329a7985e93c6 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Sat, 25 May 2013 09:10:41 +0300 Subject: [PATCH 0366/2940] Fixed typo in app.blueprints docstring --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index cc9a2f90..f04a9b07 100644 --- a/flask/app.py +++ b/flask/app.py @@ -452,7 +452,7 @@ class Flask(_PackageBoundObject): None: [_default_template_ctx_processor] } - #: all the attached blueprints in a directory by name. Blueprints + #: all the attached blueprints in a dictionary by name. Blueprints #: can be attached multiple times so this dictionary does not tell #: you how often they got attached. #: From 8bb972e5ae647650457bc4e94ff51cb5e34951dd Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 25 May 2013 19:13:48 +0200 Subject: [PATCH 0367/2940] fix minitwit/flaskr test errors, improve docs about file open mode app.open_resource needs to get called with the correct mode param (python3 will read bytes [not str] if the wrong mode is used), add mode param docs. rv.data is bytes, fix the data type we compare it with to be also bytes --- docs/patterns/sqlite3.rst | 2 +- docs/tutorial/dbinit.rst | 2 +- examples/flaskr/flaskr.py | 2 +- examples/flaskr/flaskr_tests.py | 16 ++++----- examples/minitwit/minitwit.py | 2 +- examples/minitwit/minitwit_tests.py | 56 ++++++++++++++--------------- flask/app.py | 1 + flask/helpers.py | 1 + 8 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 76fec0b2..e625d15b 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -124,7 +124,7 @@ can do that for you:: def init_db(): with app.app_context(): db = get_db() - with app.open_resource('schema.sql') as f: + with app.open_resource('schema.sql', mode='r') as f: db.cursor().executescript(f.read()) db.commit() diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index b32a8eda..1241193a 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -33,7 +33,7 @@ earlier. Just add that function below the `connect_db` function in def init_db(): with closing(connect_db()) as db: - with app.open_resource('schema.sql') as f: + with app.open_resource('schema.sql', mode='r') as f: db.cursor().executescript(f.read()) db.commit() diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 20254660..b193e94e 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -31,7 +31,7 @@ def init_db(): """Creates the database tables.""" with app.app_context(): db = get_db() - with app.open_resource('schema.sql') as f: + with app.open_resource('schema.sql', mode='r') as f: db.cursor().executescript(f.read()) db.commit() diff --git a/examples/flaskr/flaskr_tests.py b/examples/flaskr/flaskr_tests.py index cfac3782..dd16c038 100644 --- a/examples/flaskr/flaskr_tests.py +++ b/examples/flaskr/flaskr_tests.py @@ -42,21 +42,21 @@ class FlaskrTestCase(unittest.TestCase): def test_empty_db(self): """Start with a blank database.""" rv = self.app.get('/') - assert 'No entries here so far' in rv.data + assert b'No entries here so far' in rv.data def test_login_logout(self): """Make sure login and logout works""" rv = self.login(flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD']) - assert 'You were logged in' in rv.data + assert b'You were logged in' in rv.data rv = self.logout() - assert 'You were logged out' in rv.data + assert b'You were logged out' in rv.data rv = self.login(flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD']) - assert 'Invalid username' in rv.data + assert b'Invalid username' in rv.data rv = self.login(flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x') - assert 'Invalid password' in rv.data + assert b'Invalid password' in rv.data def test_messages(self): """Test that messages work""" @@ -66,9 +66,9 @@ class FlaskrTestCase(unittest.TestCase): title='', text='HTML allowed here' ), follow_redirects=True) - assert 'No entries here so far' not in rv.data - assert '<Hello>' in rv.data - assert 'HTML allowed here' in rv.data + assert b'No entries here so far' not in rv.data + assert b'<Hello>' in rv.data + assert b'HTML allowed here' in rv.data if __name__ == '__main__': diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index 2863de50..baa204f9 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -53,7 +53,7 @@ def init_db(): """Creates the database tables.""" with app.app_context(): db = get_db() - with app.open_resource('schema.sql') as f: + with app.open_resource('schema.sql', mode='r') as f: db.cursor().executescript(f.read()) db.commit() diff --git a/examples/minitwit/minitwit_tests.py b/examples/minitwit/minitwit_tests.py index 87741165..c213466d 100644 --- a/examples/minitwit/minitwit_tests.py +++ b/examples/minitwit/minitwit_tests.py @@ -63,7 +63,7 @@ class MiniTwitTestCase(unittest.TestCase): rv = self.app.post('/add_message', data={'text': text}, follow_redirects=True) if text: - assert 'Your message was recorded' in rv.data + assert b'Your message was recorded' in rv.data return rv # testing functions @@ -71,29 +71,29 @@ class MiniTwitTestCase(unittest.TestCase): def test_register(self): """Make sure registering works""" rv = self.register('user1', 'default') - assert 'You were successfully registered ' \ - 'and can login now' in rv.data + assert b'You were successfully registered ' \ + b'and can login now' in rv.data rv = self.register('user1', 'default') - assert 'The username is already taken' in rv.data + assert b'The username is already taken' in rv.data rv = self.register('', 'default') - assert 'You have to enter a username' in rv.data + assert b'You have to enter a username' in rv.data rv = self.register('meh', '') - assert 'You have to enter a password' in rv.data + assert b'You have to enter a password' in rv.data rv = self.register('meh', 'x', 'y') - assert 'The two passwords do not match' in rv.data + assert b'The two passwords do not match' in rv.data rv = self.register('meh', 'foo', email='broken') - assert 'You have to enter a valid email address' in rv.data + assert b'You have to enter a valid email address' in rv.data def test_login_logout(self): """Make sure logging in and logging out works""" rv = self.register_and_login('user1', 'default') - assert 'You were logged in' in rv.data + assert b'You were logged in' in rv.data rv = self.logout() - assert 'You were logged out' in rv.data + assert b'You were logged out' in rv.data rv = self.login('user1', 'wrongpassword') - assert 'Invalid password' in rv.data + assert b'Invalid password' in rv.data rv = self.login('user2', 'wrongpassword') - assert 'Invalid username' in rv.data + assert b'Invalid username' in rv.data def test_message_recording(self): """Check if adding messages works""" @@ -101,8 +101,8 @@ class MiniTwitTestCase(unittest.TestCase): self.add_message('test message 1') self.add_message('') rv = self.app.get('/') - assert 'test message 1' in rv.data - assert '<test message 2>' in rv.data + assert b'test message 1' in rv.data + assert b'<test message 2>' in rv.data def test_timelines(self): """Make sure that timelines work""" @@ -112,37 +112,37 @@ class MiniTwitTestCase(unittest.TestCase): self.register_and_login('bar', 'default') self.add_message('the message by bar') rv = self.app.get('/public') - assert 'the message by foo' in rv.data - assert 'the message by bar' in rv.data + assert b'the message by foo' in rv.data + assert b'the message by bar' in rv.data # bar's timeline should just show bar's message rv = self.app.get('/') - assert 'the message by foo' not in rv.data - assert 'the message by bar' in rv.data + assert b'the message by foo' not in rv.data + assert b'the message by bar' in rv.data # now let's follow foo rv = self.app.get('/foo/follow', follow_redirects=True) - assert 'You are now following "foo"' in rv.data + assert b'You are now following "foo"' in rv.data # we should now see foo's message rv = self.app.get('/') - assert 'the message by foo' in rv.data - assert 'the message by bar' in rv.data + assert b'the message by foo' in rv.data + assert b'the message by bar' in rv.data # but on the user's page we only want the user's message rv = self.app.get('/bar') - assert 'the message by foo' not in rv.data - assert 'the message by bar' in rv.data + assert b'the message by foo' not in rv.data + assert b'the message by bar' in rv.data rv = self.app.get('/foo') - assert 'the message by foo' in rv.data - assert 'the message by bar' not in rv.data + assert b'the message by foo' in rv.data + assert b'the message by bar' not in rv.data # now unfollow and check if that worked rv = self.app.get('/foo/unfollow', follow_redirects=True) - assert 'You are no longer following "foo"' in rv.data + assert b'You are no longer following "foo"' in rv.data rv = self.app.get('/') - assert 'the message by foo' not in rv.data - assert 'the message by bar' in rv.data + assert b'the message by foo' not in rv.data + assert b'the message by bar' in rv.data if __name__ == '__main__': diff --git a/flask/app.py b/flask/app.py index dc684489..77e4799c 100644 --- a/flask/app.py +++ b/flask/app.py @@ -630,6 +630,7 @@ class Flask(_PackageBoundObject): :param resource: the name of the resource. To access resources within subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. """ return open(os.path.join(self.instance_path, resource), mode) diff --git a/flask/helpers.py b/flask/helpers.py index dbbbf2e6..37d3bb49 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -838,6 +838,7 @@ class _PackageBoundObject(object): :param resource: the name of the resource. To access resources within subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. """ if mode not in ('r', 'rb'): raise ValueError('Resources can only be opened for reading') From af5576a6c50ec849a824c1ace15f187d378db771 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 25 May 2013 19:46:26 +0200 Subject: [PATCH 0368/2940] fix iterator in testsuite helpers --- flask/testsuite/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 21c9f791..90e9e020 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -15,7 +15,7 @@ import unittest from logging import StreamHandler from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr from werkzeug.http import parse_cache_control_header, parse_options_header -from flask._compat import StringIO, text_type +from flask._compat import StringIO, text_type, implements_iterator def has_encoding(name): @@ -485,6 +485,7 @@ class StreamingTestCase(FlaskTestCase): app = flask.Flask(__name__) app.testing = True called = [] + @implements_iterator class Wrapper(object): def __init__(self, gen): self._gen = gen @@ -492,7 +493,7 @@ class StreamingTestCase(FlaskTestCase): return self def close(self): called.append(42) - def next(self): + def __next__(self): return next(self._gen) @app.route('/') def index(): From 83f76585725fd380b61f35576bb1c307fe2a1a5e Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 25 May 2013 20:19:17 +0200 Subject: [PATCH 0369/2940] fix metaclass usage for py3 --- flask/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/flask/views.py b/flask/views.py index 5192c1c1..b3b61b52 100644 --- a/flask/views.py +++ b/flask/views.py @@ -9,6 +9,7 @@ :license: BSD, see LICENSE for more details. """ from .globals import request +from ._compat import with_metaclass http_method_funcs = frozenset(['get', 'post', 'head', 'options', @@ -119,7 +120,7 @@ class MethodViewType(type): return rv -class MethodView(View): +class MethodView(with_metaclass(MethodViewType, View)): """Like a regular class-based view but that dispatches requests to particular methods. For instance if you implement a method called :meth:`get` it means you will response to ``'GET'`` requests and @@ -138,8 +139,6 @@ class MethodView(View): app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter')) """ - __metaclass__ = MethodViewType - def dispatch_request(self, *args, **kwargs): meth = getattr(self, request.method.lower(), None) # if the request method is HEAD and we don't have a handler for it From 96b8ffbb29eaba834a30352554e42cf2406c7e06 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 25 May 2013 20:24:14 +0200 Subject: [PATCH 0370/2940] always import from ._compat --- flask/app.py | 2 +- flask/config.py | 2 +- flask/debughelpers.py | 2 +- flask/exthook.py | 2 +- flask/helpers.py | 2 +- flask/testing.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flask/app.py b/flask/app.py index 77e4799c..84ef1013 100644 --- a/flask/app.py +++ b/flask/app.py @@ -34,7 +34,7 @@ from .templating import DispatchingJinjaLoader, Environment, \ _default_template_ctx_processor from .signals import request_started, request_finished, got_request_exception, \ request_tearing_down, appcontext_tearing_down -from flask._compat import reraise, string_types, integer_types +from ._compat import reraise, string_types, integer_types # a lock used for logger initialization _logger_lock = Lock() diff --git a/flask/config.py b/flask/config.py index 4d9ac23a..155afa2f 100644 --- a/flask/config.py +++ b/flask/config.py @@ -14,7 +14,7 @@ import os import errno from werkzeug.utils import import_string -from flask._compat import string_types +from ._compat import string_types class ConfigAttribute(object): diff --git a/flask/debughelpers.py b/flask/debughelpers.py index f3bac185..2f8510f9 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -8,7 +8,7 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from flask._compat import implements_to_string +from ._compat import implements_to_string class UnexpectedUnicodeError(AssertionError, UnicodeError): diff --git a/flask/exthook.py b/flask/exthook.py index 89dac47b..d0d814c6 100644 --- a/flask/exthook.py +++ b/flask/exthook.py @@ -21,7 +21,7 @@ """ import sys import os -from flask._compat import reraise +from ._compat import reraise class ExtensionImporter(object): diff --git a/flask/helpers.py b/flask/helpers.py index 37d3bb49..49fd0278 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -24,7 +24,6 @@ from functools import update_wrapper from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound -from flask._compat import string_types, text_type # this was moved in 0.7 try: @@ -37,6 +36,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 # sentinel diff --git a/flask/testing.py b/flask/testing.py index ef116cf3..4c1f4550 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -13,7 +13,7 @@ from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack -from flask._compat import urlparse +from ._compat import urlparse def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): From f9e9e774646ff7cbd2df6386c7055760936a9fcd Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 25 May 2013 20:58:12 +0200 Subject: [PATCH 0371/2940] fix data types in after_request test TODO: why was that bug not causing / displaying an exception somehow? should give a TypeError in py 3.3. --- flask/testsuite/basic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 656233b8..9bbe10e3 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -443,7 +443,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): evts.append('before') @app.after_request def after_request(response): - response.data += '|after' + response.data += b'|after' evts.append('after') return response @app.route('/') @@ -453,7 +453,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'request' self.assert_not_in('after', evts) rv = app.test_client().get('/').data - self.assert_in(b'after', evts) + self.assert_in('after', evts) self.assert_equal(rv, b'request|after') def test_after_request_processing(self): From 13cc69911c6b5c742489ffe6e8c6458dec32e230 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 25 May 2013 22:01:14 +0200 Subject: [PATCH 0372/2940] fix typos --- flask/_compat.py | 2 +- flask/app.py | 10 +++++----- flask/ctx.py | 2 +- flask/helpers.py | 2 +- flask/testsuite/blueprints.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index 27f61137..7c960ac6 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -91,7 +91,7 @@ else: def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a - # dummy metaclass for one level of class instanciation that replaces + # dummy metaclass for one level of class instantiation that replaces # itself with the actual metaclass. Because of internal type checks # we also need to make sure that we downgrade the custom metaclass # for one level to something closer to type (that's why __call__ and diff --git a/flask/app.py b/flask/app.py index 84ef1013..c3bbc907 100644 --- a/flask/app.py +++ b/flask/app.py @@ -177,7 +177,7 @@ class Flask(_PackageBoundObject): #: The debug flag. Set this to `True` to enable debugging of the #: application. In debug mode the debugger will kick in when an unhandled - #: exception ocurrs and the integrated server will automatically reload + #: exception occurs and the integrated server will automatically reload #: the application if changes in the code are detected. #: #: This attribute can also be configured from the config with the `DEBUG` @@ -522,7 +522,7 @@ class Flask(_PackageBoundObject): """The name of the application. This is usually the import name with the difference that it's guessed from the run file if the import name is main. This name is used as a display name when - Flask needs the name of the application. It can be set and overriden + Flask needs the name of the application. It can be set and overridden to change the value. .. versionadded:: 0.8 @@ -697,7 +697,7 @@ class Flask(_PackageBoundObject): This injects request, session, config and g into the template context as well as everything template context processors want to inject. Note that the as of Flask 0.6, the original values - in the context will not be overriden if a context processor + in the context will not be overridden if a context processor decides to return a value with the same key. :param context: the context as a dictionary that is updated in place @@ -1045,7 +1045,7 @@ class Flask(_PackageBoundObject): app.error_handler_spec[None][404] = page_not_found Setting error handlers via assignments to :attr:`error_handler_spec` - however is discouraged as it requires fidling with nested dictionaries + however is discouraged as it requires fiddling with nested dictionaries and the special case for arbitrary exception types. The first `None` refers to the active blueprint. If the error @@ -1550,7 +1550,7 @@ class Flask(_PackageBoundObject): # When we create a response object directly, we let the constructor # set the headers and status. We do this because there can be # some extra logic involved when creating these objects with - # specific values (like defualt content type selection). + # specific values (like default content type selection). if isinstance(rv, string_types): rv = self.response_class(rv, headers=headers, status=status) headers = status = None diff --git a/flask/ctx.py b/flask/ctx.py index 320f2f0e..6ab5b1ff 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -286,7 +286,7 @@ class RequestContext(object): def push(self): """Binds the request context to the current context.""" - # If an exception ocurrs in debug mode or if context preservation is + # If an exception occurs in debug mode or if context preservation is # activated under exception situations exactly one context stays # on the stack. The rationale is that you want to access that # information under debug situations. However if someone forgets to diff --git a/flask/helpers.py b/flask/helpers.py index 49fd0278..02b66797 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -330,7 +330,7 @@ def get_template_attribute(template_name, attribute): .. versionadded:: 0.2 :param template_name: the name of the template - :param attribute: the name of the variable of macro to acccess + :param attribute: the name of the variable of macro to access """ return getattr(current_app.jinja_env.get_template(template_name).module, attribute) diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 97a196d7..b3771fde 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -105,7 +105,7 @@ class ModuleTestCase(FlaskTestCase): app = flask.Flask(__name__) admin = flask.Module(__name__, 'admin', url_prefix='/admin') @app.context_processor - def inject_all_regualr(): + def inject_all_regular(): return {'a': 1} @admin.context_processor def inject_admin(): @@ -534,7 +534,7 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() self.assertEqual(c.get('/py/foo').data, b'bp.foo') - # The rule's din't actually made it through + # The rule's didn't actually made it through rv = c.get('/py/bar') assert rv.status_code == 404 rv = c.get('/py/bar/123') From bb2e20f53fd66981190658a58e206a3f8aa4f3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sun, 26 May 2013 15:37:52 +0200 Subject: [PATCH 0373/2940] Depends on itsdangerous>=0.12 now --- setup.py | 2 +- tox.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ddc83251..a0a65cf4 100644 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ setup( install_requires=[ 'Werkzeug>=0.7', 'Jinja2>=2.4', - 'itsdangerous>=0.17' + 'itsdangerous>=0.21' ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tox.ini b/tox.ini index c699ac48..161e86fb 100644 --- a/tox.ini +++ b/tox.ini @@ -3,5 +3,4 @@ envlist = py26, py27, pypy, py33 [testenv] deps = -egit+git://github.com/mitsuhiko/werkzeug.git@sprint-branch#egg=werkzeug - -egit+git://github.com/mitsuhiko/itsdangerous.git#egg=itsdangerous commands = python run-tests.py [] From ac04bc78361d5562f8289e8efe16a2e9a97b0d01 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 26 May 2013 20:33:22 +0200 Subject: [PATCH 0374/2940] replace 1/0 by 1 // 0 to get rid of DeprecationWarning (and PEP8 issue) --- flask/testsuite/basic.py | 6 +++--- flask/testsuite/helpers.py | 10 +++++----- flask/testsuite/signals.py | 2 +- flask/testsuite/subclassing.py | 2 +- flask/testsuite/testing.py | 2 +- flask/testsuite/views.py | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 9bbe10e3..a172ed85 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -529,7 +529,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): pass @app.route('/') def fails(): - 1/0 + 1 // 0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) self.assert_in(b'Internal Server Error', rv.data) @@ -866,7 +866,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.route('/') def index(): - 1/0 + 1 // 0 c = app.test_client() if config_key is not None: app.config[config_key] = True @@ -1054,7 +1054,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/fail') def fail_func(): - 1/0 + 1 // 0 c = app.test_client() for x in range(3): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 90e9e020..2b04f73a 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -306,7 +306,7 @@ class LoggingTestCase(FlaskTestCase): @app.route('/exc') def exc(): - 1/0 + 1 // 0 with app.test_client() as c: with catch_stderr() as err: @@ -340,7 +340,7 @@ class LoggingTestCase(FlaskTestCase): @app.route('/') def index(): - 1/0 + 1 // 0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) @@ -349,7 +349,7 @@ class LoggingTestCase(FlaskTestCase): err = out.getvalue() self.assert_in('Exception on / [GET]', err) self.assert_in('Traceback (most recent call last):', err) - self.assert_in('1/0', err) + self.assert_in('1 // 0', err) self.assert_in('ZeroDivisionError:', err) def test_processor_exceptions(self): @@ -357,11 +357,11 @@ class LoggingTestCase(FlaskTestCase): @app.before_request def before_request(): if trigger == 'before': - 1/0 + 1 // 0 @app.after_request def after_request(response): if trigger == 'after': - 1/0 + 1 // 0 return response @app.route('/') def index(): diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index ffd575c1..94262f17 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -83,7 +83,7 @@ class SignalsTestCase(FlaskTestCase): @app.route('/') def index(): - 1/0 + 1 // 0 def record(sender, exception): recorded.append(exception) diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py index dbfdd499..6b81db98 100644 --- a/flask/testsuite/subclassing.py +++ b/flask/testsuite/subclassing.py @@ -30,7 +30,7 @@ class FlaskSubclassingTestCase(FlaskTestCase): @app.route('/') def index(): - 1/0 + 1 // 0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index cd96b497..a618f0b8 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -143,7 +143,7 @@ class TestToolsTestCase(FlaskTestCase): @app.route('/other') def other(): - 1/0 + 1 // 0 with app.test_client() as c: resp = c.get('/') diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py index 9dd463f2..4eee015b 100644 --- a/flask/testsuite/views.py +++ b/flask/testsuite/views.py @@ -55,9 +55,9 @@ class ViewTestCase(FlaskTestCase): class Index(flask.views.MethodView): def get(self): - 1/0 + 1 // 0 def post(self): - 1/0 + 1 // 0 class Other(Index): def get(self): From 404265110ab52ffd2a5d4991f115913aacba94a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuha=CC=88user?= Date: Sun, 26 May 2013 21:46:22 +0200 Subject: [PATCH 0375/2940] Always return a list from get_flashed_messages --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 02b66797..3dade16c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -398,7 +398,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]): _request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \ if '_flashes' in session else [] if category_filter: - flashes = filter(lambda f: f[0] in category_filter, flashes) + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) if not with_categories: return [x[1] for x in flashes] return flashes From 775c76ac5c4365deb31061d0a8ef942d0585f81d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 12:48:04 +0100 Subject: [PATCH 0376/2940] Enabled test mode for an app --- flask/testsuite/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 2b04f73a..bba66bda 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -133,6 +133,7 @@ class JSONTestCase(FlaskTestCase): class ModifiedRequest(flask.Request): url_charset = 'euc-kr' app = flask.Flask(__name__) + app.testing = True app.request_class = ModifiedRequest app.url_map.charset = 'euc-kr' From ffd9296507db6bfad1339b75c0ccdc7fa030cb67 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 12:51:12 +0100 Subject: [PATCH 0377/2940] Close request objects if they support closing. --- flask/ctx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/ctx.py b/flask/ctx.py index 6ab5b1ff..259c5e2f 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -334,6 +334,9 @@ class RequestContext(object): if exc is None: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) + request_close = getattr(self.request, 'close', None) + if request_close is not None: + request_close() clear_request = True rv = _request_ctx_stack.pop() From 47572c5b4006c54f0991587d5c07d599d0ad3325 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 14:24:29 +0100 Subject: [PATCH 0378/2940] Set the content length automatically before calling wrap_file --- flask/helpers.py | 1 + flask/testsuite/basic.py | 1 + 2 files changed, 2 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index 3dade16c..f52b5ae4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -516,6 +516,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if file is None: file = open(filename, 'rb') mtime = os.path.getmtime(filename) + headers['Content-Length'] = os.path.getsize(filename) data = wrap_file(request.environ, file) rv = current_app.response_class(data, mimetype=mimetype, headers=headers, diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index a172ed85..810b5e66 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -786,6 +786,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def test_static_files(self): app = flask.Flask(__name__) + app.testing = True rv = app.test_client().get('/static/index.html') self.assert_equal(rv.status_code, 200) self.assert_equal(rv.data.strip(), b'

Hello World!

') From eb622fb34f0b2433b21b6b5454273a597b77a6d4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 14:31:36 +0100 Subject: [PATCH 0379/2940] Fixed a whole bunch of resource warnings in the flask testsuite --- flask/testsuite/basic.py | 1 + flask/testsuite/blueprints.py | 5 +++++ flask/testsuite/helpers.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 810b5e66..8cd3a822 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -793,6 +793,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_request_context(): self.assert_equal(flask.url_for('static', filename='index.html'), '/static/index.html') + rv.close() def test_none_response(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index b3771fde..5935a473 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -172,8 +172,10 @@ class ModuleTestCase(FlaskTestCase): self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/static/test.txt') self.assert_equal(rv.data.strip(), b'Admin File') + rv.close() rv = c.get('/admin/static/css/test.css') self.assert_equal(rv.data.strip(), b'/* nested file */') + rv.close() with app.test_request_context(): self.assert_equal(flask.url_for('admin.static', filename='test.txt'), @@ -354,8 +356,10 @@ class BlueprintTestCase(FlaskTestCase): self.assert_equal(rv.data, b'Hello from the Admin') rv = c.get('/admin/static/test.txt') self.assert_equal(rv.data.strip(), b'Admin File') + rv.close() rv = c.get('/admin/static/css/test.css') self.assert_equal(rv.data.strip(), b'/* nested file */') + rv.close() # try/finally, in case other tests use this app for Blueprint tests. max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT'] @@ -405,6 +409,7 @@ class BlueprintTestCase(FlaskTestCase): rv = blueprint.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 100) + rv.close() finally: app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index bba66bda..b74ad6bb 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -159,6 +159,7 @@ class SendfileTestCase(FlaskTestCase): self.assert_equal(rv.mimetype, 'text/html') with app.open_resource('static/index.html') as f: self.assert_equal(rv.data, f.read()) + rv.close() def test_send_file_xsendfile(self): app = flask.Flask(__name__) @@ -170,6 +171,7 @@ class SendfileTestCase(FlaskTestCase): self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) self.assert_equal(rv.mimetype, 'text/html') + rv.close() def test_send_file_object(self): app = flask.Flask(__name__) @@ -180,6 +182,7 @@ class SendfileTestCase(FlaskTestCase): with app.open_resource('static/index.html') as f: self.assert_equal(rv.data, f.read()) self.assert_equal(rv.mimetype, 'text/html') + rv.close() # mimetypes + etag self.assert_equal(len(captured), 2) @@ -192,6 +195,7 @@ class SendfileTestCase(FlaskTestCase): self.assert_in('x-sendfile', rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) + rv.close() # mimetypes + etag self.assert_equal(len(captured), 2) @@ -202,6 +206,7 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file(f) self.assert_equal(rv.data, b'Test') self.assert_equal(rv.mimetype, 'application/octet-stream') + rv.close() # etags self.assert_equal(len(captured), 1) with catch_warnings() as captured: @@ -209,6 +214,7 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file(f, mimetype='text/plain') self.assert_equal(rv.data, b'Test') self.assert_equal(rv.mimetype, 'text/plain') + rv.close() # etags self.assert_equal(len(captured), 1) @@ -218,6 +224,7 @@ class SendfileTestCase(FlaskTestCase): f = StringIO('Test') rv = flask.send_file(f) self.assert_not_in('x-sendfile', rv.headers) + rv.close() # etags self.assert_equal(len(captured), 1) @@ -229,6 +236,7 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file(f, as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) self.assert_equal(value, 'attachment') + rv.close() # mimetypes + etag self.assert_equal(len(captured), 2) @@ -238,6 +246,7 @@ class SendfileTestCase(FlaskTestCase): value, options = parse_options_header(rv.headers['Content-Disposition']) self.assert_equal(value, 'attachment') self.assert_equal(options['filename'], 'index.html') + rv.close() with app.test_request_context(): rv = flask.send_file(StringIO('Test'), as_attachment=True, @@ -247,6 +256,7 @@ class SendfileTestCase(FlaskTestCase): value, options = parse_options_header(rv.headers['Content-Disposition']) self.assert_equal(value, 'attachment') self.assert_equal(options['filename'], 'index.txt') + rv.close() def test_static_file(self): app = flask.Flask(__name__) @@ -256,20 +266,24 @@ class SendfileTestCase(FlaskTestCase): rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 12 * 60 * 60) + rv.close() # Test again with direct use of send_file utility. rv = flask.send_file('static/index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 12 * 60 * 60) + rv.close() app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600 with app.test_request_context(): # Test with static file handler. rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 3600) + rv.close() # Test again with direct use of send_file utility. rv = flask.send_file('static/index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 3600) + rv.close() class StaticFileApp(flask.Flask): def get_send_file_max_age(self, filename): return 10 @@ -279,10 +293,12 @@ class SendfileTestCase(FlaskTestCase): rv = app.send_static_file('index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 10) + rv.close() # Test again with direct use of send_file utility. rv = flask.send_file('static/index.html') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, 10) + rv.close() class LoggingTestCase(FlaskTestCase): From 8aaf3025864acbb803b652495d282bc5aa7a8128 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 14:35:23 +0100 Subject: [PATCH 0380/2940] Disable direct passthrough for accessing the data attribute on newer Werkzeugs --- flask/testsuite/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index b74ad6bb..20c15262 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -158,6 +158,7 @@ class SendfileTestCase(FlaskTestCase): self.assert_true(rv.direct_passthrough) self.assert_equal(rv.mimetype, 'text/html') with app.open_resource('static/index.html') as f: + rv.direct_passthrough = False self.assert_equal(rv.data, f.read()) rv.close() @@ -179,6 +180,7 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) + rv.direct_passthrough = False with app.open_resource('static/index.html') as f: self.assert_equal(rv.data, f.read()) self.assert_equal(rv.mimetype, 'text/html') From 51042f4c9f2c68a63ad5b8ee3000a52518a4b87b Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 30 May 2013 16:00:43 +0200 Subject: [PATCH 0381/2940] fix issues in test_context_refcounts that were unnoticed yet as they did not make the test fail --- flask/testsuite/appctx.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/appctx.py b/flask/testsuite/appctx.py index 54b9014e..8524d22b 100644 --- a/flask/testsuite/appctx.py +++ b/flask/testsuite/appctx.py @@ -87,8 +87,9 @@ class AppContextTestCase(FlaskTestCase): with flask._app_ctx_stack.top: with flask._request_ctx_stack.top: pass - self.assert_true(flask._request_ctx_stack.request.environ + self.assert_true(flask._request_ctx_stack.top.request.environ ['werkzeug.request'] is not None) + return u'' c = app.test_client() c.get('/') self.assertEqual(called, ['request', 'app']) From bbfef4c406506d89c662038c11c6c67bc97b67e3 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 30 May 2013 16:02:28 +0200 Subject: [PATCH 0382/2940] flask view function may return bytes/str/unicode --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index c3bbc907..bfd615b7 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1551,7 +1551,7 @@ class Flask(_PackageBoundObject): # set the headers and status. We do this because there can be # some extra logic involved when creating these objects with # specific values (like default content type selection). - if isinstance(rv, string_types): + if isinstance(rv, string_types + (bytes, )): rv = self.response_class(rv, headers=headers, status=status) headers = status = None else: From abc1505196ba3bc9517ec142ced7c1204c3ac21b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 15:07:18 +0100 Subject: [PATCH 0383/2940] Fixed various issues on the Python 3 port --- flask/_compat.py | 4 ---- flask/json.py | 34 ++++++++++++++++++++++++++++++---- flask/testing.py | 8 ++++++-- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index 7c960ac6..edc9957a 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -47,8 +47,6 @@ if not PY2: encode_filename = _identity get_next = lambda x: x.__next__ - from urllib.parse import urlparse - else: unichr = unichr text_type = unicode @@ -86,8 +84,6 @@ else: return filename.encode('utf-8') return filename - from urlparse import urlparse - def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a diff --git a/flask/json.py b/flask/json.py index 22f6067d..9e56073c 100644 --- a/flask/json.py +++ b/flask/json.py @@ -8,10 +8,11 @@ :copyright: (c) 2012 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import io import uuid from datetime import datetime from .globals import current_app, request -from ._compat import text_type +from ._compat import text_type, PY2 from werkzeug.http import http_date @@ -33,6 +34,20 @@ __all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump', 'jsonify'] +def _wrap_reader_for_text(fp, encoding): + if isinstance(fp.read(0), bytes): + fp = io.TextIOWrapper(io.BufferedReader(fp), encoding) + return fp + + +def _wrap_writer_for_text(fp, encoding): + try: + fp.write('') + except TypeError: + fp = io.TextIOWrapper(fp, encoding) + return fp + + class JSONEncoder(_json.JSONEncoder): """The default Flask JSON encoder. This one extends the default simplejson encoder by also supporting ``datetime`` objects, ``UUID`` as well as @@ -100,13 +115,20 @@ def dumps(obj, **kwargs): and can be overriden by the simplejson ``ensure_ascii`` parameter. """ _dump_arg_defaults(kwargs) - return _json.dumps(obj, **kwargs) + encoding = kwargs.pop('encoding', None) + rv = _json.dumps(obj, **kwargs) + if encoding is not None and isinstance(rv, text_type): + rv = rv.encode(encoding) + return rv def dump(obj, fp, **kwargs): """Like :func:`dumps` but writes into a file object.""" _dump_arg_defaults(kwargs) - return _json.dump(obj, fp, **kwargs) + encoding = kwargs.pop('encoding', None) + if encoding is not None: + fp = _wrap_writer_for_text(fp, encoding) + _json.dump(obj, fp, **kwargs) def loads(s, **kwargs): @@ -115,6 +137,8 @@ def loads(s, **kwargs): application on the stack. """ _load_arg_defaults(kwargs) + if isinstance(s, bytes): + s = s.decode(kwargs.pop('encoding', None) or 'utf-8') return _json.loads(s, **kwargs) @@ -122,6 +146,8 @@ def load(fp, **kwargs): """Like :func:`loads` but reads from a file object. """ _load_arg_defaults(kwargs) + if not PY2: + fp = _wrap_reader_for_text(fp, kwargs.pop('encoding', None) or 'utf-8') return _json.load(fp, **kwargs) @@ -148,7 +174,7 @@ def jsonify(*args, **kwargs): to this function are the same as to the :class:`dict` constructor. Example usage:: - + from flask import jsonify @app.route('/_get_current_user') diff --git a/flask/testing.py b/flask/testing.py index 4c1f4550..1dc383af 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -13,7 +13,11 @@ from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack -from ._compat import urlparse + +try: + from werkzeug.urls import url_parse +except ImportError: + from urlparse import urlsplit as url_parse def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): @@ -21,7 +25,7 @@ def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): http_host = app.config.get('SERVER_NAME') app_root = app.config.get('APPLICATION_ROOT') if base_url is None: - url = urlparse(path) + url = url_parse(path) base_url = 'http://%s/' % (url.netloc or http_host or 'localhost') if app_root: base_url += app_root.lstrip('/') From 9ae8487330e0dc3a8d169d89cadf85fb11ded1ea Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 16:16:39 +0100 Subject: [PATCH 0384/2940] Fixed a broekn testcase --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index f52b5ae4..06343cf3 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -516,7 +516,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if file is None: file = open(filename, 'rb') mtime = os.path.getmtime(filename) - headers['Content-Length'] = os.path.getsize(filename) + headers['Content-Length'] = os.path.getsize(filename) data = wrap_file(request.environ, file) rv = current_app.response_class(data, mimetype=mimetype, headers=headers, From 90e3906d02780c47e813649b1e282bfd279d7cb1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 17:58:27 +0100 Subject: [PATCH 0385/2940] Fixed some test failures --- flask/testsuite/basic.py | 6 ++++-- flask/testsuite/blueprints.py | 1 + flask/testsuite/helpers.py | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 8cd3a822..bd5b2760 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -513,7 +513,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): # test that all teardown_requests get passed the same original # exception. try: - raise TypeError + raise TypeError() except: pass @app.teardown_request @@ -524,7 +524,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): # test that all teardown_requests get passed the same original # exception. try: - raise TypeError + raise TypeError() except: pass @app.route('/') @@ -1098,7 +1098,9 @@ class SubdomainTestCase(FlaskTestCase): app.register_module(mod) c = app.test_client() rv = c.get('/static/hello.txt', 'http://foo.example.com/') + rv.direct_passthrough = False self.assert_equal(rv.data.strip(), b'Hello Subdomain') + rv.close() def test_subdomain_matching(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 5935a473..3414eeaa 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -371,6 +371,7 @@ class BlueprintTestCase(FlaskTestCase): rv = c.get('/admin/static/css/test.css') cc = parse_cache_control_header(rv.headers['Cache-Control']) self.assert_equal(cc.max_age, expected_max_age) + rv.close() finally: app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 20c15262..7750ae52 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -206,6 +206,7 @@ class SendfileTestCase(FlaskTestCase): with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f) + rv.direct_passthrough = False self.assert_equal(rv.data, b'Test') self.assert_equal(rv.mimetype, 'application/octet-stream') rv.close() @@ -214,6 +215,7 @@ class SendfileTestCase(FlaskTestCase): with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f, mimetype='text/plain') + rv.direct_passthrough = False self.assert_equal(rv.data, b'Test') self.assert_equal(rv.mimetype, 'text/plain') rv.close() From f1918093ac70d589a4d67af0d77140734c06c13d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 18:15:17 +0100 Subject: [PATCH 0386/2940] Changed teardown error handling to be more reliable. --- CHANGES | 3 +++ flask/app.py | 20 +++++++++++++++++++- flask/ctx.py | 13 ++++++++----- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index fbde1e2a..17290290 100644 --- a/CHANGES +++ b/CHANGES @@ -59,6 +59,9 @@ Release date to be decided. strongly discouraged as the interface was flawed. - Python requirements changed: requiring Python 2.6 or 2.7 now to prepare for Python 3.3 port. +- Changed how the teardown system is informed about exceptions. This is now + more reliable in case something handles an exception halfway through + the error handling process. Version 0.9 ----------- diff --git a/flask/app.py b/flask/app.py index bfd615b7..28a0a59a 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1510,6 +1510,16 @@ class Flask(_PackageBoundObject): rv.allow.update(methods) return rv + def should_ignore_error(self, error): + """This is called to figure out if an error should be ignored + or not as far as the teardown system is concerned. If this + function returns `True` then the teardown handlers will not be + passed the error. + + .. versionadded:: 0.10 + """ + return False + def make_response(self, rv): """Converts the return value from a view function to a real response object that is an instance of :attr:`response_class`. @@ -1790,12 +1800,20 @@ class Flask(_PackageBoundObject): a list of headers and an optional exception context to start the response """ - with self.request_context(environ): + ctx = self.request_context(environ) + ctx.push() + error = None + try: try: response = self.full_dispatch_request() except Exception as e: + error = e response = self.make_response(self.handle_exception(e)) return response(environ, start_response) + finally: + if self.should_ignore_error(error): + error = None + ctx.auto_pop(error) @property def modules(self): diff --git a/flask/ctx.py b/flask/ctx.py index 259c5e2f..5e1ee2e3 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -352,6 +352,13 @@ class RequestContext(object): if app_ctx is not None: app_ctx.pop(exc) + def auto_pop(self, exc): + if self.request.environ.get('flask._preserve_context') or \ + (exc is not None and self.app.preserve_context_on_exception): + self.preserved = True + else: + self.pop(exc) + def __enter__(self): self.push() return self @@ -362,11 +369,7 @@ class RequestContext(object): # access the request object in the interactive shell. Furthermore # the context can be force kept alive for the test client. # See flask.testing for how this works. - if self.request.environ.get('flask._preserve_context') or \ - (tb is not None and self.app.preserve_context_on_exception): - self.preserved = True - else: - self.pop(exc_value) + self.auto_pop(exc_value) def __repr__(self): return '<%s \'%s\' [%s] of %s>' % ( From e07dcb5562c336975e31ce014aa33d2ab1f5ac98 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 18:17:04 +0100 Subject: [PATCH 0387/2940] Adjusted a testcase for Python 3 --- flask/testsuite/ext.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 1a7a4a5a..5cc3df43 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -121,7 +121,10 @@ class ExtImportHookTestCase(FlaskTestCase): self.assert_true(tb.tb_frame.f_globals is globals()) # reraise() adds a second frame so we need to skip that one too. + # On PY3 we even have another one :( next = tb.tb_next.tb_next + if not PY2: + next = next.tb_next self.assert_in('flask_broken/__init__.py', next.tb_frame.f_code.co_filename) From b8aa9fed9a6837d29c472c642539dcf3496479ab Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 18:19:01 +0100 Subject: [PATCH 0388/2940] Added tox-test command --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4b5e4fe2..b67c8a9f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ -.PHONY: clean-pyc ext-test test test-with-mem upload-docs docs audit +.PHONY: clean-pyc ext-test test tox-test test-with-mem upload-docs docs audit all: clean-pyc test test: python run-tests.py +tox-test: + tox + test-with-mem: RUN_FLASK_MEMORY_TESTS=1 python run-tests.py From 6bd5dfad0c24d1a1077e7b1e00ffd6d64ba07a31 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 30 May 2013 21:39:54 +0100 Subject: [PATCH 0389/2940] Test Flask against werkzeug master --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 161e86fb..a8782e00 100644 --- a/tox.ini +++ b/tox.ini @@ -2,5 +2,5 @@ envlist = py26, py27, pypy, py33 [testenv] -deps = -egit+git://github.com/mitsuhiko/werkzeug.git@sprint-branch#egg=werkzeug +deps = -egit+git://github.com/mitsuhiko/werkzeug.git#egg=werkzeug commands = python run-tests.py [] From e9fa24cfa367754c4435224cb9840f0a84a5faec Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 31 May 2013 00:56:09 +0100 Subject: [PATCH 0390/2940] Make travis install development werkzeug --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c9c592d..b0b18d1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,9 @@ python: - pypy - 3.3 -install: pip install --editable . +install: + - pip install git+git://github.com/mitsuhiko/werkzeug.git#egg=Werkzeug + - pip install --editable . script: make test From 1c8c21abd529249db567b701400cf764555941b5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 31 May 2013 00:59:54 +0100 Subject: [PATCH 0391/2940] Let travis notify the #pocoo irc channel --- .travis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b0b18d1c..8b2607d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,11 @@ branches: - website notifications: - # Disable travis notifications until they figured out how to hide - # their own builder failure from us. Travis currently fails way - # too many times by itself. email: false + irc: + channels: + - "chat.freenode.net#pocoo" + on_success: change + on_failure: always + use_notice: true + skip_join: true From 3d9055b3b73b1d09f933bfd070f704e9e8bdff2a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 1 Jun 2013 00:20:00 +0100 Subject: [PATCH 0392/2940] Added the JSONIFY_PRETTYPRINT_REGULAR config variable. This fixes #725 --- CHANGES | 1 + docs/config.rst | 7 ++++++- flask/app.py | 3 ++- flask/json.py | 10 +++++++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 17290290..7be9c77a 100644 --- a/CHANGES +++ b/CHANGES @@ -62,6 +62,7 @@ Release date to be decided. - Changed how the teardown system is informed about exceptions. This is now more reliable in case something handles an exception halfway through the error handling process. +- Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable. Version 0.9 ----------- diff --git a/docs/config.rst b/docs/config.rst index 134fe36d..0dbb71d7 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -149,6 +149,11 @@ The following configuration values are used internally by Flask: unicode strings. ``jsonfiy`` will automatically encode it in ``utf-8`` then for transport for instance. +``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) + jsonify responses will be pretty printed + if they are not requested by an + XMLHttpRequest object (controlled by + the ``X-Requested-With`` header) ================================= ========================================= .. admonition:: More on ``SERVER_NAME`` @@ -192,7 +197,7 @@ The following configuration values are used internally by Flask: ``PREFERRED_URL_SCHEME`` .. versionadded:: 0.10 - ``JSON_AS_ASCII`` + ``JSON_AS_ASCII``, ``JSONIFY_PRETTYPRINT_REGULAR`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 5bd3d1de..86f2a212 100644 --- a/flask/app.py +++ b/flask/app.py @@ -290,7 +290,8 @@ class Flask(_PackageBoundObject): 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'PREFERRED_URL_SCHEME': 'http', - 'JSON_AS_ASCII': True + 'JSON_AS_ASCII': True, + 'JSONIFY_PRETTYPRINT_REGULAR': True, }) #: The rule object to use for URL rules created. This is used by diff --git a/flask/json.py b/flask/json.py index 9e56073c..e43c4ed9 100644 --- a/flask/json.py +++ b/flask/json.py @@ -194,8 +194,16 @@ def jsonify(*args, **kwargs): For security reasons only objects are supported toplevel. For more information about this, have a look at :ref:`json-security`. + This function's response will be pretty printed if it was not requested + with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless + the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false. + .. versionadded:: 0.2 """ + indent = None + if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] \ + and not request.is_xhr: + indent = 2 return current_app.response_class(dumps(dict(*args, **kwargs), - indent=None if request.is_xhr else 2), + indent=indent), mimetype='application/json') From 77d293cf49e586f03fbea96d0bae237bc7ed230f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 1 Jun 2013 19:24:03 +0100 Subject: [PATCH 0393/2940] Order JSON keys by default to avoid trashing HTTP caches --- CHANGES | 2 ++ docs/config.rst | 13 ++++++++++++- flask/app.py | 1 + flask/json.py | 2 ++ flask/testsuite/helpers.py | 40 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7be9c77a..378488f6 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,8 @@ Release date to be decided. more reliable in case something handles an exception halfway through the error handling process. - Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable. +- Flask now orders JSON keys by default to not trash HTTP caches due to + different hash seeds between different workers. Version 0.9 ----------- diff --git a/docs/config.rst b/docs/config.rst index 0dbb71d7..ced2ad82 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -149,6 +149,17 @@ The following configuration values are used internally by Flask: unicode strings. ``jsonfiy`` will automatically encode it in ``utf-8`` then for transport for instance. +``JSON_SORT_KEYS`` By default Flask will serialize JSON + objects in a way that the keys are + ordered. This is done in order to + ensure that independent of the hash seed + of the dictionary the return value will + be consistent to not trash external HTTP + caches. You can override the default + behavior by changing this variable. + This is not recommended but might give + you a performance improvement on the + cost of cachability. ``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) jsonify responses will be pretty printed if they are not requested by an @@ -197,7 +208,7 @@ The following configuration values are used internally by Flask: ``PREFERRED_URL_SCHEME`` .. versionadded:: 0.10 - ``JSON_AS_ASCII``, ``JSONIFY_PRETTYPRINT_REGULAR`` + ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 86f2a212..7b286571 100644 --- a/flask/app.py +++ b/flask/app.py @@ -291,6 +291,7 @@ class Flask(_PackageBoundObject): 'TRAP_HTTP_EXCEPTIONS': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, + 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, }) diff --git a/flask/json.py b/flask/json.py index e43c4ed9..875af67e 100644 --- a/flask/json.py +++ b/flask/json.py @@ -92,10 +92,12 @@ class JSONDecoder(_json.JSONDecoder): def _dump_arg_defaults(kwargs): """Inject default arguments for dump functions.""" + kwargs.setdefault('sort_keys', True) if current_app: kwargs.setdefault('cls', 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']) def _load_arg_defaults(kwargs): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 7750ae52..c88c6241 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -148,6 +148,46 @@ class JSONTestCase(FlaskTestCase): if not has_encoding('euc-kr'): test_modified_url_encoding = None + def test_json_key_sorting(self): + app = flask.Flask(__name__) + app.testing = True + self.assert_equal(app.config['JSON_SORT_KEYS'], True) + d = dict.fromkeys(range(20), 'foo') + + @app.route('/') + def index(): + return flask.jsonify(values=d) + + c = app.test_client() + rv = c.get('/') + lines = [x.strip() for x in rv.data.strip().decode('utf-8').splitlines()] + self.assert_equal(lines, [ + '{', + '"values": {', + '"0": "foo",', + '"1": "foo",', + '"2": "foo",', + '"3": "foo",', + '"4": "foo",', + '"5": "foo",', + '"6": "foo",', + '"7": "foo",', + '"8": "foo",', + '"9": "foo",', + '"10": "foo",', + '"11": "foo",', + '"12": "foo",', + '"13": "foo",', + '"14": "foo",', + '"15": "foo",', + '"16": "foo",', + '"17": "foo",', + '"18": "foo",', + '"19": "foo"', + '}', + '}' + ]) + class SendfileTestCase(FlaskTestCase): From c629f69e698fd815969e91e5c8c59323c61a256c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Jun 2013 11:54:22 +0100 Subject: [PATCH 0394/2940] Make the JSON module work better in the absence of an application context --- flask/json.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flask/json.py b/flask/json.py index 875af67e..2437a20c 100644 --- a/flask/json.py +++ b/flask/json.py @@ -92,18 +92,22 @@ class JSONDecoder(_json.JSONDecoder): def _dump_arg_defaults(kwargs): """Inject default arguments for dump functions.""" - kwargs.setdefault('sort_keys', True) if current_app: kwargs.setdefault('cls', current_app.json_encoder) if not current_app.config['JSON_AS_ASCII']: kwargs.setdefault('ensure_ascii', False) kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS']) + else: + kwargs.setdefault('sort_keys', True) + kwargs.setdefault('cls', JSONEncoder) def _load_arg_defaults(kwargs): """Inject default arguments for load functions.""" if current_app: kwargs.setdefault('cls', current_app.json_decoder) + else: + kwargs.setdefault('cls', JSONDecoder) def dumps(obj, **kwargs): From 0190b770a1e3339c2cd96e6a44f44083aeeebb54 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Jun 2013 17:23:53 +0100 Subject: [PATCH 0395/2940] Removed a bunch of code from _compat --- flask/_compat.py | 42 ++------------------------------------ flask/helpers.py | 5 ++++- flask/testsuite/helpers.py | 4 ++-- 3 files changed, 8 insertions(+), 43 deletions(-) diff --git a/flask/_compat.py b/flask/_compat.py index edc9957a..1d4e9808 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -13,13 +13,10 @@ import sys PY2 = sys.version_info[0] == 2 -PYPY = hasattr(sys, 'pypy_translation_info') _identity = lambda x: x if not PY2: - unichr = chr - range_type = range text_type = str string_types = (str,) integer_types = (int, ) @@ -28,29 +25,17 @@ if not PY2: itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items()) - import pickle - from io import BytesIO, StringIO - NativeStringIO = StringIO + from io import StringIO def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value - ifilter = filter - imap = map - izip = zip - intern = sys.intern - - implements_iterator = _identity implements_to_string = _identity - encode_filename = _identity - get_next = lambda x: x.__next__ else: - unichr = unichr text_type = unicode - range_type = xrange string_types = (str, unicode) integer_types = (int, long) @@ -58,32 +43,15 @@ else: itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() - import cPickle as pickle - from cStringIO import StringIO as BytesIO, StringIO - NativeStringIO = BytesIO + from cStringIO import StringIO as StringIO exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') - from itertools import imap, izip, ifilter - intern = intern - - def implements_iterator(cls): - cls.next = cls.__next__ - del cls.__next__ - return cls - def implements_to_string(cls): cls.__unicode__ = cls.__str__ cls.__str__ = lambda x: x.__unicode__().encode('utf-8') return cls - get_next = lambda x: x.next - - def encode_filename(filename): - if isinstance(filename, unicode): - return filename.encode('utf-8') - return filename - def with_metaclass(meta, *bases): # This requires a bit of explanation: the basic idea is to make a @@ -103,9 +71,3 @@ def with_metaclass(meta, *bases): return type.__new__(cls, name, (), d) return meta(name, bases, d) return metaclass('temporary_class', None, {}) - - -try: - from urllib.parse import quote_from_bytes as url_quote -except ImportError: - from urllib import quote as url_quote diff --git a/flask/helpers.py b/flask/helpers.py index 06343cf3..1e7c87f0 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -18,9 +18,12 @@ from time import time from zlib import adler32 from threading import RLock from werkzeug.routing import BuildError -from werkzeug.urls import url_quote from functools import update_wrapper +try: + from werkzeug.urls import url_quote +except ImportError: + from urlparse import quote as url_quote from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index c88c6241..b02025a9 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -15,7 +15,7 @@ import unittest from logging import StreamHandler from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr from werkzeug.http import parse_cache_control_header, parse_options_header -from flask._compat import StringIO, text_type, implements_iterator +from flask._compat import StringIO, text_type def has_encoding(name): @@ -546,7 +546,6 @@ class StreamingTestCase(FlaskTestCase): app = flask.Flask(__name__) app.testing = True called = [] - @implements_iterator class Wrapper(object): def __init__(self, gen): self._gen = gen @@ -556,6 +555,7 @@ class StreamingTestCase(FlaskTestCase): called.append(42) def __next__(self): return next(self._gen) + next = __next__ @app.route('/') def index(): def generate(): From 6dfe9332606a497a1931f9cad4a150f2866e3cda Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Jun 2013 17:25:04 +0100 Subject: [PATCH 0396/2940] Removed an unnecessary as statement --- flask/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/_compat.py b/flask/_compat.py index 1d4e9808..c3428845 100644 --- a/flask/_compat.py +++ b/flask/_compat.py @@ -43,7 +43,7 @@ else: itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() - from cStringIO import StringIO as StringIO + from cStringIO import StringIO exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') From 1b40b3b573f5d98cf9fbc453305cd535c0b2578d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Jun 2013 21:47:28 +0100 Subject: [PATCH 0397/2940] Fixed request context preservation and teardown handler interaction. --- CHANGES | 3 +++ flask/app.py | 7 +++++++ flask/ctx.py | 16 +++++++++++++++- flask/testsuite/basic.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 378488f6..5bd76b97 100644 --- a/CHANGES +++ b/CHANGES @@ -62,6 +62,9 @@ Release date to be decided. - Changed how the teardown system is informed about exceptions. This is now more reliable in case something handles an exception halfway through the error handling process. +- Request context preservation in debug mode now keeps the exception + information around which means that teardown handlers are able to + distinguish error from success cases. - Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable. - Flask now orders JSON keys by default to not trash HTTP caches due to different hash seeds between different workers. diff --git a/flask/app.py b/flask/app.py index 7b286571..b52af9b2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1707,6 +1707,13 @@ class Flask(_PackageBoundObject): rv = func(exc) request_tearing_down.send(self, exc=exc) + # If this interpreter supports clearing the exception information + # we do that now. This will only go into effect on Python 2.x, + # on 3.x it disappears automatically at the end of the exception + # stack. + if hasattr(sys, 'exc_clear'): + sys.exc_clear() + def do_teardown_appcontext(self, exc=None): """Called when an application context is popped. This works pretty much the same as :meth:`do_teardown_request` but for the application diff --git a/flask/ctx.py b/flask/ctx.py index 5e1ee2e3..6ea3158f 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -235,6 +235,10 @@ class RequestContext(object): # is pushed the preserved context is popped. self.preserved = False + # remembers the exception for pop if there is one in case the context + # preservation kicks in. + self._preserved_exc = None + # Functions that should be executed after the request on the response # object. These will be called before the regular "after_request" # functions. @@ -296,7 +300,7 @@ class RequestContext(object): # functionality is not active in production environments. top = _request_ctx_stack.top if top is not None and top.preserved: - top.pop() + top.pop(top._preserved_exc) # Before we push the request context we have to ensure that there # is an application context. @@ -331,9 +335,18 @@ class RequestContext(object): clear_request = False if not self._implicit_app_ctx_stack: self.preserved = False + self._preserved_exc = None if exc is None: exc = sys.exc_info()[1] self.app.do_teardown_request(exc) + + # If this interpreter supports clearing the exception information + # we do that now. This will only go into effect on Python 2.x, + # on 3.x it disappears automatically at the end of the exception + # stack. + if hasattr(sys, 'exc_clear'): + sys.exc_clear() + request_close = getattr(self.request, 'close', None) if request_close is not None: request_close() @@ -356,6 +369,7 @@ class RequestContext(object): if self.request.environ.get('flask._preserve_context') or \ (exc is not None and self.app.preserve_context_on_exception): self.preserved = True + self._preserved_exc = exc else: self.pop(exc) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index bd5b2760..50ffcef8 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -1070,6 +1070,40 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_true(flask._request_ctx_stack.top is None) self.assert_true(flask._app_ctx_stack.top is None) + def test_preserve_remembers_exception(self): + app = flask.Flask(__name__) + app.debug = True + errors = [] + + @app.route('/fail') + def fail_func(): + 1 // 0 + + @app.route('/success') + def success_func(): + return 'Okay' + + @app.teardown_request + def teardown_handler(exc): + errors.append(exc) + + c = app.test_client() + + # After this failure we did not yet call the teardown handler + with self.assert_raises(ZeroDivisionError): + c.get('/fail') + self.assert_equal(errors, []) + + # But this request triggers it, and it's an error + c.get('/success') + self.assert_equal(len(errors), 2) + self.assert_true(isinstance(errors[0], ZeroDivisionError)) + + # At this point another request does nothing. + c.get('/success') + self.assert_equal(len(errors), 3) + self.assert_equal(errors[1], None) + class SubdomainTestCase(FlaskTestCase): From 56d3b74488346a03b74fb4a9fd633cc5f79191d8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Jun 2013 23:24:28 +0100 Subject: [PATCH 0398/2940] Added a test for non-ascii routing --- flask/testsuite/basic.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 50ffcef8..3a60eb81 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -963,6 +963,18 @@ class BasicFunctionalityTestCase(FlaskTestCase): expected = '/login' self.assert_equal(url, expected) + def test_nonascii_pathinfo(self): + app = flask.Flask(__name__) + app.testing = True + + @app.route(u'/киртест') + def index(): + return 'Hello World!' + + c = app.test_client() + rv = c.get(u'/киртест') + self.assert_equal(rv.data, b'Hello World!') + def test_debug_mode_complains_after_first_request(self): app = flask.Flask(__name__) app.debug = True From ef72b78042d7feffc864e7f2da3f62835fc63ee8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 3 Jun 2013 12:25:08 +0100 Subject: [PATCH 0399/2940] Imply the |safe on tojson in templates and change escaping logic --- CHANGES | 2 ++ docs/api.rst | 5 ++--- docs/patterns/jquery.rst | 11 ++++++++--- docs/templating.rst | 6 ++---- flask/app.py | 9 +-------- flask/json.py | 27 ++++++++++++++++++++------- flask/testsuite/helpers.py | 17 +++++++++++------ 7 files changed, 46 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index 5bd76b97..ff0a3e83 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Release date to be decided. ``template_filter`` method family. - Set the content-length header for x-sendfile. - ``tojson`` filter now does not escape script blocks in HTML5 parsers. +- ``tojson`` used in templates is now safe by default due. This was + allowed due to the different escaping behavior. - Flask will now raise an error if you attempt to register a new function on an already used endpoint. - Added wrapper module around simplejson and added default serialization diff --git a/docs/api.rst b/docs/api.rst index 27333079..096741ae 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -364,7 +364,8 @@ JSON module: The :func:`~htmlsafe_dumps` function of this json module is also available as filter called ``|tojson`` in Jinja2. Note that inside `script` tags no escaping must take place, so make sure to disable escaping -with ``|safe`` if you intend to use it inside `script` tags: +with ``|safe`` if you intend to use it inside `script` tags unless +you are using Flask 0.10 which implies that: .. sourcecode:: html+jinja @@ -372,8 +373,6 @@ with ``|safe`` if you intend to use it inside `script` tags: doSomethingWith({{ user.username|tojson|safe }}); -Note that the ``|tojson`` filter escapes forward slashes properly. - .. autofunction:: jsonify .. autofunction:: dumps diff --git a/docs/patterns/jquery.rst b/docs/patterns/jquery.rst index 7aaa2803..9de99f61 100644 --- a/docs/patterns/jquery.rst +++ b/docs/patterns/jquery.rst @@ -63,9 +63,10 @@ like this: $SCRIPT_ROOT = {{ request.script_root|tojson|safe }}; -The ``|safe`` is necessary so that Jinja does not escape the JSON encoded -string with HTML rules. Usually this would be necessary, but we are -inside a `script` block here where different rules apply. +The ``|safe`` is necessary in Flask before 0.10 so that Jinja does not +escape the JSON encoded string with HTML rules. Usually this would be +necessary, but we are inside a `script` block here where different rules +apply. .. admonition:: Information for Pros @@ -76,6 +77,10 @@ inside a `script` block here where different rules apply. escape slashes for you (``{{ ""|tojson|safe }}`` is rendered as ``"<\/script>"``). + In Flask 0.10 it goes a step further and escapes all HTML tags with + unicode escapes. This makes it possible for Flask to automatically + mark the result as HTML safe. + JSON View Functions ------------------- diff --git a/docs/templating.rst b/docs/templating.rst index b6e1fc0a..4e432333 100644 --- a/docs/templating.rst +++ b/docs/templating.rst @@ -106,8 +106,8 @@ by Jinja2 itself: fly. Note that inside `script` tags no escaping must take place, so make - sure to disable escaping with ``|safe`` if you intend to use it inside - `script` tags: + sure to disable escaping with ``|safe`` before Flask 0.10 if you intend + to use it inside `script` tags: .. sourcecode:: html+jinja @@ -115,8 +115,6 @@ by Jinja2 itself: doSomethingWith({{ user.username|tojson|safe }}); - That the ``|tojson`` filter escapes forward slashes properly for you. - Controlling Autoescaping ------------------------ diff --git a/flask/app.py b/flask/app.py index b52af9b2..271c29cd 100644 --- a/flask/app.py +++ b/flask/app.py @@ -659,7 +659,7 @@ class Flask(_PackageBoundObject): session=session, g=g ) - rv.filters['tojson'] = json.htmlsafe_dumps + rv.filters['tojson'] = json.tojson_filter return rv def create_global_jinja_loader(self): @@ -1707,13 +1707,6 @@ class Flask(_PackageBoundObject): rv = func(exc) request_tearing_down.send(self, exc=exc) - # If this interpreter supports clearing the exception information - # we do that now. This will only go into effect on Python 2.x, - # on 3.x it disappears automatically at the end of the exception - # stack. - if hasattr(sys, 'exc_clear'): - sys.exc_clear() - def do_teardown_appcontext(self, exc=None): """Called when an application context is popped. This works pretty much the same as :meth:`do_teardown_request` but for the application diff --git a/flask/json.py b/flask/json.py index 2437a20c..d1cda5ae 100644 --- a/flask/json.py +++ b/flask/json.py @@ -15,6 +15,7 @@ from .globals import current_app, request from ._compat import text_type, PY2 from werkzeug.http import http_date +from jinja2 import Markup # Use the same json implementation as itsdangerous on which we # depend anyways. @@ -160,18 +161,26 @@ def load(fp, **kwargs): def htmlsafe_dumps(obj, **kwargs): """Works exactly like :func:`dumps` but is safe for use in ``"|tojson|safe }}') - self.assert_equal(rv, '"<\\/script>"') - rv = render('{{ "<\0/script>"|tojson|safe }}') - self.assert_equal(rv, '"<\\u0000\\/script>"') - rv = render('{{ " + +

{% endif %} {% endif %} -