From b64fa73467424b8e6c4efa565d4d5bc2ae414ab7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 14:37:02 +0200 Subject: [PATCH 0001/3143] HEAD is 0.8-dev --- 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 19560123..1c5e3ba2 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.7' +__version__ = '0.8-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 d2a9266b..1809e05e 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ def run_tests(): setup( name='Flask', - version='0.7', + version='0.8-dev', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 62e7f5cea62c5fc735c1993c3c11a29825b71966 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 14:45:11 +0200 Subject: [PATCH 0002/3143] Better uploads --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 604c8a40..7b14422f 100644 --- a/Makefile +++ b/Makefile @@ -26,9 +26,9 @@ upload-docs: $(MAKE) -C docs html dirhtml latex $(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 - scp -r docs/_build/dirhtml/* pocoo.org:/var/www/flask.pocoo.org/docs/ - scp -r docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf - scp -r docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/ + 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 docs: $(MAKE) -C docs html From 0ff7fe2a378d89308e3e6b2740ac1469157fa088 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Jun 2011 06:14:39 -0700 Subject: [PATCH 0003/3143] Typo fix in code samples for Pluggable Views doc. --- docs/views.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/views.rst b/docs/views.rst index a5b960e7..fc22d1d7 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -86,7 +86,7 @@ For instance you can write a class like this:: And then you can register it like this:: - app.add_url_view('/about', RenderTemplateView.as_view( + app.add_url_rule('/about', view_func=RenderTemplateView.as_view( 'about_page', template_name='about.html')) Method Hints @@ -108,7 +108,7 @@ information:: ... ... - app.add_url_view('/myview', MyView.as_view('myview')) + app.add_url_rule('/myview', view_func=MyView.as_view('myview')) Method Based Dispatching ------------------------ @@ -130,7 +130,7 @@ easily do that. Each HTTP method maps to a function with the same name user = User.from_form_data(request.form) ... - app.add_url_view('/users/', UserAPI.as_view('users')) + app.add_url_rule('/users/', view_func=UserAPI.as_view('users')) That way you also don't have to provide the :attr:`~flask.views.View.methods` attribute. It's automatically set based From ee001e192d9178ff43c0511dc4292299b0a534d3 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 28 Jun 2011 06:25:52 -0700 Subject: [PATCH 0004/3143] Add a link to pluggable views docs in the changelog. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index b9302834..e02a393b 100644 --- a/CHANGES +++ b/CHANGES @@ -56,7 +56,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 pluggable views (class based views). +- Implemented generic :ref:`views` (class based views). Version 0.6.1 ------------- From 5afeac0c19b6c9ca6456dff368724492822ea165 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 16:06:51 +0200 Subject: [PATCH 0005/3143] Fixed release script :( --- scripts/make-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make-release.py b/scripts/make-release.py index 349365c3..9955b13e 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -85,7 +85,7 @@ def set_setup_version(version): def build_and_upload(): - Popen([sys.executable, 'setup.py', 'sdist', 'release']).wait() + Popen([sys.executable, 'setup.py', 'release', 'sdist', 'upload']).wait() def fail(message, *args): From d0821edca5a075383a98d88d2916b5991897421b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 16:08:19 +0200 Subject: [PATCH 0006/3143] Fixed link to the upgrade script. github changed links apparently --- docs/upgrading.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 7fcef042..d5b05923 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -45,7 +45,7 @@ good. To apply the upgrade script do the following: 1. Download the script: `flask-07-upgrade.py - `_ + `_ 2. Run it in the directory of your application:: python flask-07-upgrade.py > patchfile.diff From e2fed6c3a771514a0f6ed2d10e239ef901305713 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 16:11:01 +0200 Subject: [PATCH 0007/3143] Fixed another typo --- docs/blueprints.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index 175ce69f..b9fa0e4a 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -22,7 +22,7 @@ Blueprints in Flask are intended for these cases: larger applications; a project could instantiate an application object, initialize several extensions, and register a collection of blueprints. * Register a blueprint on an application at a URL prefix and/or subdomain. - Paremeters in the URL prefix/subdomain become common view arguments + Parameters in the URL prefix/subdomain become common view arguments (with defaults) across all view functions in the blueprint. * Register a blueprint multiple times on an application with different URL rules. From 57d9a2a1187b306c6997a3dcbac25837a54862f2 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 28 Jun 2011 12:00:01 -0400 Subject: [PATCH 0008/3143] Import with statement in helpers.py, #264. --- flask/helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index 0db7eb44..2a841236 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -9,6 +9,8 @@ :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement + import os import sys import posixpath From 5aabc4070096b6e9dca56b62b8afb514d641f5cd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 23:36:39 +0200 Subject: [PATCH 0009/3143] Added changelog entry Signed-off-by: Armin Ronacher --- CHANGES | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index e02a393b..1e559167 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,13 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.7.1 +------------- + +Bugfix release, release date to be decided. + +- Added missing future import that broke 2.5 compatibility. + Version 0.7 ----------- From 7d4c7847085dfe19893ff2a7d4c0d1bd0fbb7981 Mon Sep 17 00:00:00 2001 From: Luit van Drongelen Date: Wed, 29 Jun 2011 01:06:03 -0700 Subject: [PATCH 0010/3143] Missed the -w option in uWSGI deployment docs. --- docs/deploying/uwsgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 6f373731..bdee15ba 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -36,7 +36,7 @@ Or, if you prefer: .. sourcecode:: text - $ uwsgi -s /tmp/uwsgi.sock myapp:app + $ uwsgi -s /tmp/uwsgi.sock -w myapp:app Configuring nginx ----------------- From c26a6dd50c847d083cf444616eb5a8c99fb8b43b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 29 Jun 2011 15:36:02 +0200 Subject: [PATCH 0011/3143] Admin -> Backend --- 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 bc498cf2..129fce7a 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -42,7 +42,7 @@ are combined by the dispatcher middleware into a larger one that dispatched based on prefix. For example you could have your main application run on `/` and your -backend interface on `/admin`:: +backend interface on `/backend`:: from werkzeug.wsgi import DispatcherMiddleware from frontend_app import application as frontend From a101cfc35be437ad0e4f56da0d92f14c27abda67 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 29 Jun 2011 18:31:48 +0200 Subject: [PATCH 0012/3143] Worked around a werkzeug bug with redirects --- CHANGES | 1 + flask/app.py | 6 ++++++ tests/flask_tests.py | 15 +++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/CHANGES b/CHANGES index 1e559167..e611c4ab 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Version 0.7.1 Bugfix release, release date to be decided. - Added missing future import that broke 2.5 compatibility. +- Fixed an infinite redirect issue with blueprints. Version 0.7 ----------- diff --git a/flask/app.py b/flask/app.py index eb7ba023..dff4272b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -706,6 +706,12 @@ class Flask(_PackageBoundObject): if 'OPTIONS' not in methods: methods = tuple(methods) + ('OPTIONS',) provide_automatic_options = True + + # 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 + options['defaults'] = options.get('defaults') or None + rule = self.url_rule_class(rule, methods=methods, **options) rule.provide_automatic_options = provide_automatic_options self.url_map.add(rule) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index e04c2458..fc99cd16 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1364,6 +1364,21 @@ class BlueprintTestCase(unittest.TestCase): self.assertEqual(c.get('/fe2').data.strip(), '/fe') self.assertEqual(c.get('/be').data.strip(), '/fe') + def test_empty_url_defaults(self): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/', defaults={'page': 1}) + @bp.route('/page/') + def something(page): + return str(page) + + app = flask.Flask(__name__) + app.register_blueprint(bp) + + c = app.test_client() + self.assertEqual(c.get('/').data, '1') + self.assertEqual(c.get('/page/2').data, '2') + class SendfileTestCase(unittest.TestCase): From 510823c1592abe4c57e051e9d21bd134ab7f07a7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 28 Jun 2011 23:37:03 +0200 Subject: [PATCH 0013/3143] Bumped version number for this branch to 0.7.1 --- 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 1c5e3ba2..e675eacf 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.8-dev' +__version__ = '0.7.1-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 1809e05e..7d2d4158 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ def run_tests(): setup( name='Flask', - version='0.8-dev', + version='0.7.1-dev', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 21d4a054e399a42ac448814116580a5c62991fce Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 29 Jun 2011 18:35:11 +0200 Subject: [PATCH 0014/3143] Fixed a bug in the release script Signed-off-by: Armin Ronacher --- scripts/make-release.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make-release.py b/scripts/make-release.py index 9955b13e..301e4d6d 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -33,7 +33,7 @@ def parse_changelog(): if change_info: break - match = re.match(r'^released on (\w+\s+\d+\w+\s+\d+)' + match = re.match(r'released on (\w+\s+\d+\w+\s+\d+)' r'(?:, codename (.*))?(?i)', change_info) if match is None: continue From 7e1ebae3a2879b989cae4d27bb8dd1dc8a1313ee Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 29 Jun 2011 18:37:08 +0200 Subject: [PATCH 0015/3143] Another fix for the script Signed-off-by: Armin Ronacher --- scripts/make-release.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/make-release.py b/scripts/make-release.py index 301e4d6d..574cb75d 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -33,8 +33,8 @@ def parse_changelog(): if change_info: break - match = re.match(r'released on (\w+\s+\d+\w+\s+\d+)' - r'(?:, codename (.*))?(?i)', change_info) + match = re.search(r'released on (\w+\s+\d+\w+\s+\d+)' + r'(?:, codename (.*))?(?i)', change_info) if match is None: continue From c6dbcd572b4d207701c44caea71097d198089a16 Mon Sep 17 00:00:00 2001 From: Hadley Rich Date: Thu, 30 Jun 2011 22:44:28 +1200 Subject: [PATCH 0016/3143] Trivial fix for PathDispatcher example usage --- 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 129fce7a..93b4af96 100644 --- a/docs/patterns/appdispatch.rst +++ b/docs/patterns/appdispatch.rst @@ -167,4 +167,4 @@ falls back to another application if the creator function returns `None`:: if user is not None: return create_app(user) - application = PathDispatcher('example.com', default_app, make_app) + application = PathDispatcher(default_app, make_app) From 8ba6673670ec58e9cf32c6525d1903bc8f9b66d9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 1 Jul 2011 00:50:18 +0200 Subject: [PATCH 0017/3143] Fixed a changelog entry --- docs/upgrading.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index d5b05923..1d91596d 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -193,10 +193,11 @@ to upgrade. What changed? location however. If you want to continue serving static files you need to tell the constructor explicitly the path to the static folder (which can be relative to the blueprint's module path). -- Rendering templates was simplified. Now the general syntax is - ``blueprint-shortname:template-name`` for rendering templates instead - of ``blueprint-shortname/template-name`` which was confusing and often - clashed with templates from the global template loader. +- Rendering templates was simplified. Now the blueprints can provide + template folders which are added to a general template searchpath. + This means that you need to add another subfolder with the blueprint's + name into that folder if you want ``blueprintname/template.html`` as + the template name. If you continue to use the `Module` object which is deprecated, Flask will restore the previous behavior as good as possible. However we strongly From 0fb2e4c84859d9d31fe2170df3f9d51e36a3f3c0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 1 Jul 2011 00:52:06 +0200 Subject: [PATCH 0018/3143] More invalid doc fixing --- docs/upgrading.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 1d91596d..df084e51 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -189,10 +189,12 @@ to upgrade. What changed? some unnecessary leading dots in your code if you're not using modules. - Blueprints do not automatically provide static folders. They will - still export templates from a folder called `templates` next to their - location however. If you want to continue serving static files you - need to tell the constructor explicitly the path to the static folder - (which can be relative to the blueprint's module path). + also no longer automatically export templates from a folder called + `templates` next to their location however but it can be enabled from + the constructor. Same with static files: if you want to continue + serving static files you need to tell the constructor explicitly the + path to the static folder (which can be relative to the blueprint's + module path). - Rendering templates was simplified. Now the blueprints can provide template folders which are added to a general template searchpath. This means that you need to add another subfolder with the blueprint's From 15372661af2974577d5c1d15fdaf88452661c2a9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 1 Jul 2011 00:56:06 +0200 Subject: [PATCH 0019/3143] Removed leftover from no longer supported blueprint template loading code. --- flask/templating.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask/templating.py b/flask/templating.py index d50691b3..147f49d0 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -117,8 +117,6 @@ def render_template(template_name, **context): """ ctx = _request_ctx_stack.top ctx.app.update_template_context(context) - if template_name[:1] == ':': - template_name = ctx.request.blueprint + template_name return _render(ctx.app.jinja_env.get_template(template_name), context, ctx.app) From bd473c158788bd2c1153fc8fe45831aaa648824a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 6 Jul 2011 10:16:56 +0200 Subject: [PATCH 0020/3143] Fixed an issue that broke url processors for blueprints. Added testcases --- flask/blueprints.py | 4 +-- tests/flask_tests.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/flask/blueprints.py b/flask/blueprints.py index a0dfd521..0c3a7d0f 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -56,7 +56,7 @@ class BlueprintSetupState(object): #: A dictionary with URL defaults that is added to each and every #: URL that was defined with the blueprint. - self.url_defaults = dict(self.blueprint.url_defaults) + self.url_defaults = dict(self.blueprint.url_values_defaults) self.url_defaults.update(self.options.get('url_defaults', ())) def add_url_rule(self, rule, endpoint=None, view_func=None, **options): @@ -102,7 +102,7 @@ class Blueprint(_PackageBoundObject): self.view_functions = {} if url_defaults is None: url_defaults = {} - self.url_defaults = url_defaults + self.url_values_defaults = url_defaults def record(self, func): """Registers a function that is called when the blueprint is diff --git a/tests/flask_tests.py b/tests/flask_tests.py index fc99cd16..e13dfd36 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -830,6 +830,37 @@ class BasicFunctionalityTestCase(unittest.TestCase): rv = c.post('/accept', data={'myfile': 'foo' * 100}) assert rv.data == '42' + def test_url_processors(self): + app = flask.Flask(__name__) + + @app.url_defaults + def add_language_code(endpoint, values): + if flask.g.lang_code is not None and \ + app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values.setdefault('lang_code', flask.g.lang_code) + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code', None) + + @app.route('//') + def index(): + return flask.url_for('about') + + @app.route('//about') + def about(): + return flask.url_for('something_else') + + @app.route('/foo') + def something_else(): + return flask.url_for('about', lang_code='en') + + c = app.test_client() + + self.assertEqual(c.get('/de/').data, '/de/about') + self.assertEqual(c.get('/de/about').data, '/foo') + self.assertEqual(c.get('/foo').data, '/en/about') + class JSONTestCase(unittest.TestCase): @@ -1309,6 +1340,33 @@ class BlueprintTestCase(unittest.TestCase): self.assertEqual(c.get('/1/bar').data, u'23') self.assertEqual(c.get('/2/bar').data, u'19') + def test_blueprint_url_processors(self): + bp = flask.Blueprint('frontend', __name__, url_prefix='/') + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', flask.g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code') + + @bp.route('/') + def index(): + return flask.url_for('.about') + + @bp.route('/about') + def about(): + return flask.url_for('.index') + + app = flask.Flask(__name__) + app.register_blueprint(bp) + + c = app.test_client() + + self.assertEqual(c.get('/de/').data, '/de/about') + self.assertEqual(c.get('/de/about').data, '/de/') + def test_templates_and_static(self): from blueprintapp import app c = app.test_client() From 343d8a94e7ecd1c937685592482de71dcc7b6888 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 6 Jul 2011 10:18:03 +0200 Subject: [PATCH 0021/3143] Added changelog entry Signed-off-by: Armin Ronacher --- CHANGES | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES b/CHANGES index e611c4ab..640e0d9b 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,14 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.7.2 +------------- + +Bugfix release, released on July 6th 2011 + +- Fixed an issue with URL processors not properly working on + blueprints. + Version 0.7.1 ------------- From 515e946b020584db7f236eb8ad139f9f32de305f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 6 Jul 2011 10:21:23 +0200 Subject: [PATCH 0022/3143] master is 0.8-dev --- 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 e675eacf..1c5e3ba2 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.7.1-dev' +__version__ = '0.8-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 7d2d4158..1809e05e 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ def run_tests(): setup( name='Flask', - version='0.7.1-dev', + version='0.8-dev', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 1c05f47c3b7c6674aadfe5636078ffa1aa81d7a1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 6 Jul 2011 10:26:59 +0200 Subject: [PATCH 0023/3143] Forgot to cherry-pick the release date for 0.7.1 in master --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 640e0d9b..b414f5ad 100644 --- a/CHANGES +++ b/CHANGES @@ -14,7 +14,7 @@ Bugfix release, released on July 6th 2011 Version 0.7.1 ------------- -Bugfix release, release date to be decided. +Bugfix release, released on June 29th 2011 - Added missing future import that broke 2.5 compatibility. - Fixed an infinite redirect issue with blueprints. From de3b6ab5ee6f14d8e280b7aa0f9193b265b3d6dc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 6 Jul 2011 11:33:02 +0200 Subject: [PATCH 0024/3143] Fixed a broken sentence about blueprints. --- docs/blueprints.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index b9fa0e4a..c71d11bb 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -41,7 +41,8 @@ will have separate configs and will be managed at the WSGI layer. Blueprints instead provide separation at the Flask level, share application config, and can change an application object as necessary with being registered. The downside is that you cannot unregister a blueprint -once application without having to destroy the whole application object. +once an application was created without having to destroy the whole +application object. The Concept of Blueprints ------------------------- From d63fb818c14ea6acb36942cd66f5ed00dfa3682b Mon Sep 17 00:00:00 2001 From: Bastian Hoyer Date: Wed, 6 Jul 2011 16:07:05 +0200 Subject: [PATCH 0025/3143] small error in tutorial --- docs/tutorial/dbcon.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 1d9d41f9..8f9e4595 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -22,7 +22,7 @@ decorators:: Functions marked with :meth:`~flask.Flask.before_request` are called before a request and passed no arguments. Functions marked with -:meth:`~flask.Flask.teardown_request` are called after a request and +:meth:`~flask.Flask.after_request` are called after a request and passed the response that will be sent to the client. They have to return that response object or a different one. They are however not guaranteed to be executed if an exception is raised, this is where functions marked with From 1a61b12dbf8909ec7dd2f630b285338a8547e912 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 11:26:53 +0200 Subject: [PATCH 0026/3143] Added a row for Flask 0.8 to the changelog --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index b414f5ad..f4684b56 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.8 +----------- + +Relase date to be decided, codename to be chosen. + Version 0.7.2 ------------- From 0fccfe711fda524f216f82263bb83787a75035fd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 11:27:22 +0200 Subject: [PATCH 0027/3143] Refactored session interface --- CHANGES | 4 ++++ docs/api.rst | 25 +++++++++++++++++++++++++ flask/__init__.py | 1 - flask/app.py | 38 ++++++++++++++++++++++---------------- flask/ctx.py | 2 +- flask/session.py | 38 +++++++------------------------------- 6 files changed, 59 insertions(+), 49 deletions(-) diff --git a/CHANGES b/CHANGES index f4684b56..0db9003e 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ Version 0.8 Relase date to be decided, codename to be chosen. +- Refactored session support into a session interface so that + the implementation of the sessions can be changed without + having to override the Flask class. + Version 0.7.2 ------------- diff --git a/docs/api.rst b/docs/api.rst index b21ec2ce..99071f87 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -195,9 +195,34 @@ To access the current session you can use the :class:`session` object: session will be deleted when the user closes the browser. +Session Interface +----------------- + +.. versionadded:: 0.7 + +The session interface provides a simple way to replace the session +implementation that Flask is using. + +.. currentmodule:: flask.sessions + +.. autoclass:: SessionInterface + :members: + +.. autoclass:: SecureCookieSessionInterface + :members: + +.. autoclass:: NullSession + :members: + +.. autoclass:: SessionMixin + :members: + + Application Globals ------------------- +.. currentmodule:: flask + To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in threaded environments. Flask provides you with a special object that diff --git a/flask/__init__.py b/flask/__init__.py index 1c5e3ba2..930859da 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -27,7 +27,6 @@ from .ctx import has_request_context from .module import Module from .blueprints import Blueprint from .templating import render_template, render_template_string -from .session import Session # the signals from .signals import signals_available, template_rendered, request_started, \ diff --git a/flask/app.py b/flask/app.py index dff4272b..162ce4f2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -27,7 +27,7 @@ from .wrappers import Request, Response from .config import ConfigAttribute, Config from .ctx import RequestContext from .globals import _request_ctx_stack, request -from .session import Session, _NullSession +from .sessions import SecureCookieSessionInterface from .module import blueprint_is_module from .templating import DispatchingJinjaLoader, Environment, \ _default_template_ctx_processor @@ -211,6 +211,12 @@ class Flask(_PackageBoundObject): #: .. versionadded:: 0.7 test_client_class = None + #: the session interface to use. By default an instance of + #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. + #: + #: .. versionadded:: 0.7 + session_interface = SecureCookieSessionInterface() + def __init__(self, import_name, static_path=None, static_url_path=None, static_folder='static', template_folder='templates'): _PackageBoundObject.__init__(self, import_name, @@ -580,32 +586,32 @@ class Flask(_PackageBoundObject): def open_session(self, request): """Creates or opens a new session. Default implementation stores all session data in a signed cookie. This requires that the - :attr:`secret_key` is set. + :attr:`secret_key` is set. Instead of overriding this method + we recommend replacing the :class:`session_interface`. :param request: an instance of :attr:`request_class`. """ - key = self.secret_key - if key is not None: - return Session.load_cookie(request, self.session_cookie_name, - secret_key=key) + return self.session_interface.open_session(self, request) def save_session(self, session, response): """Saves the session if it needs updates. For the default - implementation, check :meth:`open_session`. + implementation, check :meth:`open_session`. Instead of overriding this + method we recommend replacing the :class:`session_interface`. :param session: the session to be saved (a :class:`~werkzeug.contrib.securecookie.SecureCookie` object) :param response: an instance of :attr:`response_class` """ - expires = domain = None - if session.permanent: - expires = datetime.utcnow() + self.permanent_session_lifetime - if self.config['SERVER_NAME'] is not None: - # chop of the port which is usually not supported by browsers - domain = '.' + self.config['SERVER_NAME'].rsplit(':', 1)[0] - session.save_cookie(response, self.session_cookie_name, - expires=expires, httponly=True, domain=domain) + return self.session_interface.save_session(self, session, response) + + def make_null_session(self): + """Creates a new instance of a missing session. Instead of overriding + this method we recommend replacing the :class:`session_interface`. + + .. versionadded:: 0.7 + """ + return self.session_interface.make_null_session(self) def register_module(self, module, **options): """Registers a module with this application. The keyword argument @@ -1184,7 +1190,7 @@ class Flask(_PackageBoundObject): """ ctx = _request_ctx_stack.top bp = ctx.request.blueprint - if not isinstance(ctx.session, _NullSession): + 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: diff --git a/flask/ctx.py b/flask/ctx.py index 3ea43b97..a189b28f 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -122,7 +122,7 @@ class RequestContext(object): # request context (e.g. flask-sqlalchemy). self.session = self.app.open_session(self.request) if self.session is None: - self.session = _NullSession() + self.session = self.app.make_null_session() def pop(self): """Pops the request context and unbinds it by doing that. This will diff --git a/flask/session.py b/flask/session.py index df2d8773..bfe196b0 100644 --- a/flask/session.py +++ b/flask/session.py @@ -3,41 +3,17 @@ flask.session ~~~~~~~~~~~~~ - Implements cookie based sessions based on Werkzeug's secure cookie - system. + This module used to flask with the session global so we moved it + over to flask.sessions :copyright: (c) 2010 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from werkzeug.contrib.securecookie import SecureCookie +from warnings import warn +warn(DeprecationWarning('please use flask.sessions instead')) +from .sessions import * -class Session(SecureCookie): - """Expands the session with support for switching between permanent - and non-permanent sessions. - """ - - def _get_permanent(self): - return self.get('_permanent', False) - - def _set_permanent(self, value): - self['_permanent'] = bool(value) - - permanent = property(_get_permanent, _set_permanent) - del _get_permanent, _set_permanent - - -class _NullSession(Session): - """Class used to generate nicer error messages if sessions are not - available. Will still allow read-only access to the empty session - but fail on setting. - """ - - def _fail(self, *args, **kwargs): - raise RuntimeError('the session is unavailable because no secret ' - 'key was set. Set the secret_key on the ' - 'application to something unique and secret.') - __setitem__ = __delitem__ = clear = pop = popitem = \ - update = setdefault = _fail - del _fail +Session = SecureCookieSession +_NullSession = NullSession From 10c99e95e98844ba191e4767550b0dc19fa6d34c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 12:25:25 +0200 Subject: [PATCH 0028/3143] Added missing sessions module --- flask/sessions.py | 168 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 flask/sessions.py diff --git a/flask/sessions.py b/flask/sessions.py new file mode 100644 index 00000000..c082b237 --- /dev/null +++ b/flask/sessions.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +""" + flask.sessions + ~~~~~~~~~~~~~~ + + Implements cookie based sessions based on Werkzeug's secure cookie + system. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from datetime import datetime +from werkzeug.contrib.securecookie import SecureCookie + + +class SessionMixin(object): + """Expands a basic dictionary with an accessors that are expected + by Flask extensions and users for the session. + """ + + def _get_permanent(self): + return self.get('_permanent', False) + + def _set_permanent(self, value): + self['_permanent'] = bool(value) + + permanent = property(_get_permanent, _set_permanent) + del _get_permanent, _set_permanent + + new = False + modified = True + + +class SecureCookieSession(SecureCookie, SessionMixin): + """Expands the session with support for switching between permanent + and non-permanent sessions. + """ + + +class NullSession(SecureCookieSession): + """Class used to generate nicer error messages if sessions are not + available. Will still allow read-only access to the empty session + but fail on setting. + """ + + def _fail(self, *args, **kwargs): + raise RuntimeError('the session is unavailable because no secret ' + 'key was set. Set the secret_key on the ' + 'application to something unique and secret.') + __setitem__ = __delitem__ = clear = pop = popitem = \ + update = setdefault = _fail + del _fail + + +class SessionInterface(object): + """The basic interface you have to implement in order to replace the + default session interface which uses werkzeug's securecookie + implementation. The only methods you have to implement are + :meth:`open_session` and :meth:`save_session`, the others have + useful defaults which you don't need to change. + + The session object returned by the :meth:`open_session` method has to + provide a dictionary like interface plus the properties and methods + from the :class:`SessionMixin`. We recommend just subclassing a dict + and adding that mixin:: + + class Session(dict, SessionMixin): + pass + + If :meth:`open_session` returns `None` Flask will call into + :meth:`make_null_session` to create a session that acts as replacement + if the session support cannot work because some requirement is not + fulfilled. The default :class:`NullSession` class that is created + will complain that the secret key was not set. + + To replace the session interface on an application all you have to do + is to assign :attr:`flask.Flask.session_interface`:: + + app = Flask(__name__) + app.session_interface = MySessionInterface() + + .. versionadded:: 0.7 + """ + + #: :meth:`make_null_session` will look here for the class that should + #: be created when a null session is requested. Likewise the + #: :meth:`is_null_session` method will perform a typecheck against + #: this type. + null_session_class = NullSession + + 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 + error. This mainly aids the user experience because the job of the + null session is to still support lookup without complaining but + modifications are answered with a helpful error message of what + failed. + + This creates an instance of :attr:`null_session_class` by default. + """ + return self.null_session_class() + + def is_null_session(self, obj): + """Checks if a given object is a null session. Null sessions are + not asked to be saved. + + This checks if the object is an instance of :attr:`null_session_class` + by default. + """ + return isinstance(obj, self.null_session_class) + + def get_cookie_domain(self, app): + """Helpful helper method that returns the cookie domain that should + be used for the session cookie if session cookies are used. + """ + 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] + + def get_expiration_time(self, app, session): + """A helper method that returns an expiration date for the session + or `None` if the session is linked to the browser session. The + default implementation returns now + the permanent session + lifetime configured on the application. + """ + if session.permanent: + return datetime.utcnow() + app.permanent_session_lifetime + + def open_session(self, app, request): + """This method has to be implemented and must either return `None` + in case the loading failed because of a configuration error or an + instance of a session object which implements a dictionary like + interface + the methods and attributes on :class:`SessionMixin`. + """ + raise NotImplementedError() + + def save_session(self, app, session, response): + """This is called for actual sessions returned by :meth:`open_session` + at the end of the request. This is still called during a request + context so if you absolutely need access to the request you can do + that. + """ + raise NotImplementedError() + + +class SecureCookieSessionInterface(SessionInterface): + """The cookie session interface that uses the Werkzeug securecookie + as client side session backend. + """ + session_class = SecureCookieSession + + 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) + + def save_session(self, app, session, response): + expires = self.get_expiration_time(app, session) + domain = self.get_cookie_domain(app) + if session.modified and not session: + response.delete_cookie(app.session_cookie_name, + domain=domain) + else: + session.save_cookie(response, app.session_cookie_name, + expires=expires, httponly=True, domain=domain) From 5cbfbd229d4cc524e33b1e922b5297264227ed74 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 12:29:58 +0200 Subject: [PATCH 0029/3143] Documented a change in the session system --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0db9003e..c22a6626 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Relase date to be decided, codename to be chosen. - Refactored session support into a session interface so that the implementation of the sessions can be changed without having to override the Flask class. +- Empty session cookies are now deleted properly automatically. Version 0.7.2 ------------- From 7290981cef49058b9c53f8a4465cf85537c0d22d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 12:31:57 +0200 Subject: [PATCH 0030/3143] More docstrings for the session mixin --- flask/sessions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/sessions.py b/flask/sessions.py index c082b237..abad920a 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -25,10 +25,17 @@ class SessionMixin(object): def _set_permanent(self, value): self['_permanent'] = bool(value) + #: this reflects the ``'_permanent'`` key in the dict. permanent = property(_get_permanent, _set_permanent) del _get_permanent, _set_permanent + #: some session backends can tell you if a session is new, but that is + #: not necessarily guaranteed. Use with caution. new = False + + #: for some backends this will always be `True`, but some backends will + #: default this to false and detect changes in the dictionary for as + #: long as changes do not happen on mutable structures in the session. modified = True From 10f8dc18b8d43c2776c9381f681263778674bf0d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 13:11:32 +0200 Subject: [PATCH 0031/3143] More doc updates --- flask/sessions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/sessions.py b/flask/sessions.py index abad920a..8f59a4f7 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -30,12 +30,14 @@ class SessionMixin(object): del _get_permanent, _set_permanent #: some session backends can tell you if a session is new, but that is - #: not necessarily guaranteed. Use with caution. + #: not necessarily guaranteed. Use with caution. The default mixin + #: implementation just hardcodes `False` in. new = False #: for some backends this will always be `True`, but some backends will #: default this to false and detect changes in the dictionary for as #: long as changes do not happen on mutable structures in the session. + #: The default mixin implementation just hardcodes `True` in. modified = True From f29ec355e99e052f98df46875aeb8e319b8e1beb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 7 Jul 2011 13:14:15 +0200 Subject: [PATCH 0032/3143] Backwards compatibility import --- docs/upgrading.rst | 10 ++++++++++ flask/__init__.py | 3 +++ 2 files changed, 13 insertions(+) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index df084e51..154b51a7 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -19,6 +19,16 @@ installation, make sure to pass it the ``-U`` parameter:: $ easy_install -U Flask +Version 0.8 +----------- + +Flask introduced a new session interface system. We also noticed that +there was a naming collision between `flask.session` the module that +implements sessions and :data:`flask.session` which is the global session +object. With that introduction we moved the implementation details for +the session system into a new module called :mod:`flask.sessions`. If you +used the previously undocumented session support we urge you to upgrade. + Version 0.7 ----------- diff --git a/flask/__init__.py b/flask/__init__.py index 930859da..88bf40e8 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -35,3 +35,6 @@ from .signals import signals_available, template_rendered, request_started, \ # only import json if it's available if json_available: from .helpers import json + +# backwards compat, goes away in 1.0 +from .sessions import SecureCookieSession as Session From deb513c7fe5a285e2bdeff99424e243d7b4f7829 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 8 Jul 2011 14:38:49 +0200 Subject: [PATCH 0033/3143] The session interface is new in 0.8, not 0.7 --- docs/api.rst | 2 +- flask/sessions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 99071f87..b5254996 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -198,7 +198,7 @@ To access the current session you can use the :class:`session` object: Session Interface ----------------- -.. versionadded:: 0.7 +.. versionadded:: 0.8 The session interface provides a simple way to replace the session implementation that Flask is using. diff --git a/flask/sessions.py b/flask/sessions.py index 8f59a4f7..ee006cda 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -89,7 +89,7 @@ class SessionInterface(object): app = Flask(__name__) app.session_interface = MySessionInterface() - .. versionadded:: 0.7 + .. versionadded:: 0.8 """ #: :meth:`make_null_session` will look here for the class that should From 585bf02ee0d23d020f8a24db645c308d912b62e0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 8 Jul 2011 19:20:40 +0200 Subject: [PATCH 0034/3143] Better session logic. --- flask/sessions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index ee006cda..e3fd7fb9 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -169,9 +169,10 @@ class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): expires = self.get_expiration_time(app, session) domain = self.get_cookie_domain(app) - if session.modified and not session: - response.delete_cookie(app.session_cookie_name, - domain=domain) - else: - session.save_cookie(response, app.session_cookie_name, - expires=expires, httponly=True, domain=domain) + if not session: + if session.modified: + response.delete_cookie(app.session_cookie_name, + domain=domain) + return + session.save_cookie(response, app.session_cookie_name, + expires=expires, httponly=True, domain=domain) From d90b1f1705b7bda17743406b4eac5511bdbb702a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 8 Jul 2011 19:21:50 +0200 Subject: [PATCH 0035/3143] Backout last change, save_cookie by itself does that --- flask/sessions.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index e3fd7fb9..ee006cda 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -169,10 +169,9 @@ class SecureCookieSessionInterface(SessionInterface): def save_session(self, app, session, response): expires = self.get_expiration_time(app, session) domain = self.get_cookie_domain(app) - if not session: - if session.modified: - response.delete_cookie(app.session_cookie_name, - domain=domain) - return - session.save_cookie(response, app.session_cookie_name, - expires=expires, httponly=True, domain=domain) + if session.modified and not session: + response.delete_cookie(app.session_cookie_name, + domain=domain) + else: + session.save_cookie(response, app.session_cookie_name, + expires=expires, httponly=True, domain=domain) From ebb65b81e1279060bd75d211e5e32aecf0fa3932 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 10 Jul 2011 13:33:52 +0200 Subject: [PATCH 0036/3143] Removed an unnecessary import. --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 162ce4f2..48369ee4 100644 --- a/flask/app.py +++ b/flask/app.py @@ -13,7 +13,7 @@ from __future__ import with_statement import sys from threading import Lock -from datetime import timedelta, datetime +from datetime import timedelta from itertools import chain from werkzeug import ImmutableDict From 99c2defb431642f36d6869ff84922dced06c158a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 10 Jul 2011 13:34:21 +0200 Subject: [PATCH 0037/3143] Added an example of how to postprocess requests in the testing docs. --- docs/testing.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/testing.rst b/docs/testing.rst index a196e2b7..098e81be 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -236,6 +236,20 @@ need to call :meth:`~flask.Flask.preprocess_request` yourself:: This can be necessary to open database connections or something similar depending on how your application was designed. +If you want to call the :meth:`~flask.Flask.after_request` functions you +need to call into :meth:`~flask.Flask.process_response` which however +requires that you pass it a response object:: + + app = flask.Flask(__name__) + + with app.test_request_context('/?name=Peter'): + resp = Response('...') + resp = app.process_response(resp) + ... + +This in general is less useful because at that point you can directly +start using the test client. + Keeping the Context Around -------------------------- From c8663e8dab230325037f8988880945340c331486 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 10 Jul 2011 13:35:26 +0200 Subject: [PATCH 0038/3143] Fixed a typo in the docs. --- docs/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index 098e81be..ed5765ea 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -221,7 +221,7 @@ application factories (see :ref:`app-factories`). Note however that if you are using a test request context, the :meth:`~flask.Flask.before_request` functions are not automatically called -same fore :meth:`~flask.Flask.after_request` functions. However +same for :meth:`~flask.Flask.after_request` functions. However :meth:`~flask.Flask.teardown_request` functions are indeed executed when the test request context leaves the `with` block. If you do want the :meth:`~flask.Flask.before_request` functions to be called as well, you From 441c8a5b93aca67f61d584950f351e144f48b10d Mon Sep 17 00:00:00 2001 From: ThomasWaldmann Date: Sun, 10 Jul 2011 09:25:08 -0700 Subject: [PATCH 0039/3143] fixed typo --- 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 f00211a6..778a5a6b 100644 --- a/docs/patterns/urlprocessors.rst +++ b/docs/patterns/urlprocessors.rst @@ -32,7 +32,7 @@ Consider an application like this:: g.lang_code = lang_code ... -This is an awful lot of reptition as you have to handle the language code +This is an awful lot of repetition as you have to handle the language code setting on the :data:`~flask.g` object yourself in every single function. Sure, a decorator could be used to simplify this, but if you want to generate URLs from one function to another you would have to still provide From e140bad3f8cceb464d190c0abb4e02895f5c5f3d Mon Sep 17 00:00:00 2001 From: ThomasWaldmann Date: Sun, 10 Jul 2011 09:28:26 -0700 Subject: [PATCH 0040/3143] fixed typo --- docs/reqcontext.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 832f0e66..3b49e1d5 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -157,7 +157,7 @@ request handling as they are bound to the lifecycle of the popped, the :meth:`~flask.Flask.teardown_request` functions are called. This is important to know if the life of the request context is prolonged -by using the test client in a with statement of when using the request +by using the test client in a with statement or when using the request context from the command line:: with app.test_client() as client: From fd679638ea991fbdd5d7318bc9451f0bd5532224 Mon Sep 17 00:00:00 2001 From: Jesse Dubay Date: Mon, 11 Jul 2011 15:25:37 -0700 Subject: [PATCH 0041/3143] typo fix: flask -> flag --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 48369ee4..47254bed 100644 --- a/flask/app.py +++ b/flask/app.py @@ -112,7 +112,7 @@ class Flask(_PackageBoundObject): #: configuration key. Defaults to `False`. debug = ConfigAttribute('DEBUG') - #: The testing flask. Set this to `True` to enable the test mode of + #: The testing flag. Set this to `True` to enable the test mode of #: Flask extensions (and in the future probably also Flask itself). #: For example this might activate unittest helpers that have an #: additional runtime cost which should not be enabled by default. From 1a249e338927efe58228bc70bfa3cf754526ff8a Mon Sep 17 00:00:00 2001 From: consigliere Date: Mon, 11 Jul 2011 15:28:11 -0700 Subject: [PATCH 0042/3143] Corrected decorators to remove duplicate teardown_request() and add after_request() --- docs/tutorial/dbcon.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 8f9e4595..b19cb14c 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -9,7 +9,7 @@ connection in all our functions so it makes sense to initialize them before each request and shut them down afterwards. Flask allows us to do that with the :meth:`~flask.Flask.before_request`, -:meth:`~flask.Flask.teardown_request` and :meth:`~flask.Flask.teardown_request` +:meth:`~flask.Flask.after_request` and :meth:`~flask.Flask.teardown_request` decorators:: @app.before_request From 7dcf6cbc3141d100e229b4a781f67ab1286af055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bartoszkiewicz?= Date: Tue, 12 Jul 2011 21:51:53 +0200 Subject: [PATCH 0043/3143] Don't use deprecated flask.session in flask.ctx. --- flask/ctx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index a189b28f..0943d10a 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -12,7 +12,6 @@ from werkzeug.exceptions import HTTPException from .globals import _request_ctx_stack -from .session import _NullSession from .module import blueprint_is_module From 9d899cd63e539b92262bbc6f101e7b5408bb31fc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 13 Jul 2011 14:04:23 +0200 Subject: [PATCH 0044/3143] Fixed a typo. This closes #278 --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 48369ee4..a48e8f63 100644 --- a/flask/app.py +++ b/flask/app.py @@ -82,7 +82,7 @@ class Flask(_PackageBoundObject): extension will look for the code in your application that triggered an SQL query in debug mode. If the import name is not properly set up, that debugging information is lost. (For example it would only - pick up SQL queries in `yourapplicaiton.app` and not + pick up SQL queries in `yourapplication.app` and not `yourapplication.views.frontend`) .. versionadded:: 0.5 From 3b31df81aeccb889810b4b49ce6e608e67ebeeea Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 14 Jul 2011 14:18:42 +0200 Subject: [PATCH 0045/3143] View functions can opt out of the default OPTIONS implementation --- CHANGES | 2 ++ flask/app.py | 17 +++++++++++++---- tests/flask_tests.py | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index c22a6626..f9f3c82d 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,8 @@ Relase date to be decided, codename to be chosen. the implementation of the sessions can be changed without having to override the Flask class. - Empty session cookies are now deleted properly automatically. +- View functions can now opt out of getting the automatic + OPTIONS implementation. Version 0.7.2 ------------- diff --git a/flask/app.py b/flask/app.py index 67e8298a..db9d2af0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -703,15 +703,24 @@ class Flask(_PackageBoundObject): endpoint = _endpoint_from_view_func(view_func) options['endpoint'] = endpoint methods = options.pop('methods', None) + # if the methods are not given and the view_func object knows its # methods we can use that instead. If neither exists, we go with # a tuple of only `GET` as default. if methods is None: methods = getattr(view_func, 'methods', None) or ('GET',) - provide_automatic_options = False - if 'OPTIONS' not in methods: - methods = tuple(methods) + ('OPTIONS',) - provide_automatic_options = True + + # starting with Flask 0.8 the view_func object can disable and + # force-enable the automatic options handling. + provide_automatic_options = getattr(view_func, + 'provide_automatic_options', None) + + if provide_automatic_options is None: + if 'OPTIONS' not in methods: + methods = tuple(methods) + ('OPTIONS',) + provide_automatic_options = True + else: + provide_automatic_options = False # 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 diff --git a/tests/flask_tests.py b/tests/flask_tests.py index e13dfd36..095f4bad 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -194,6 +194,23 @@ class BasicFunctionalityTestCase(unittest.TestCase): rv = app.test_client().open('/', method='OPTIONS') assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + def test_options_handling_disabled(self): + app = flask.Flask(__name__) + def index(): + return 'Hello World!' + index.provide_automatic_options = False + app.route('/')(index) + rv = app.test_client().open('/', method='OPTIONS') + assert rv.status_code == 405 + + app = flask.Flask(__name__) + def index2(): + return 'Hello World!' + index2.provide_automatic_options = True + app.route('/', methods=['OPTIONS'])(index2) + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['OPTIONS'] + def test_request_dispatching(self): app = flask.Flask(__name__) @app.route('/') From 76796c326d9abc243034847c41aa61f80ba3d653 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 14 Jul 2011 14:25:14 +0200 Subject: [PATCH 0046/3143] Documented view function options --- docs/api.rst | 38 ++++++++++++++++++++++++++++++++++++++ flask/app.py | 4 ++++ 2 files changed, 42 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index b5254996..94a38ad5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -451,3 +451,41 @@ Class Based Views .. autoclass:: flask.views.MethodView :members: + +.. _view-func-options: + +View Function Options +--------------------- + +For internal usage the view functions can have some attributes attached to +customize behavior the view function would normally not have control over. +The following attributes can be provided optionally to either override +some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior: + +- `__name__`: The name of a function is by default used as endpoint. If + endpoint is provided explicitly this value is used. Additionally this + will be prefixed with the name of the blueprint by default which + cannot be customized from the function itself. + +- `methods`: If methods are not provided when the URL rule is added, + Flask will look on the view function object itself is an `methods` + attribute exists. If it does, it will pull the information for the + methods from there. + +- `provide_automatic_options`: if this attribute is set Flask will + either force enable or disable the automatic implementation of the + HTTP `OPTIONS` response. This can be useful when working with + decorators that want to customize the `OPTIONS` response on a per-view + basis. + +Full example:: + + def index(): + if request.method == 'OPTIONS': + # custom options handling here + ... + return 'Hello World!' + index.provide_automatic_options = False + index.methods = ['GET', 'OPTIONS'] + + app.add_url_rule('/', index) diff --git a/flask/app.py b/flask/app.py index db9d2af0..8ca74e30 100644 --- a/flask/app.py +++ b/flask/app.py @@ -678,6 +678,10 @@ class Flask(_PackageBoundObject): app.view_functions['index'] = index + If a view function is provided some defaults can be specified directly + on the view function. For more information refer to + :ref:`view-func-options`. + .. versionchanged:: 0.2 `view_func` parameter added. From 6ca321c97989705b354ecb4fa06aed3ca4360542 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 14 Jul 2011 14:25:50 +0200 Subject: [PATCH 0047/3143] Added a versionadded --- docs/api.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 94a38ad5..ddeed4a6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -489,3 +489,6 @@ Full example:: index.methods = ['GET', 'OPTIONS'] app.add_url_rule('/', index) + +.. versionadded:: 0.8 + The `provide_automatic_options` functionality was added. From 2866ccda1fe19a42adc735d75abaf304f8aed27b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 15 Jul 2011 18:03:48 +0200 Subject: [PATCH 0048/3143] Switch to explicit Werkzeug imports --- flask/__init__.py | 3 ++- flask/app.py | 6 +++--- flask/config.py | 2 +- flask/globals.py | 2 +- flask/helpers.py | 8 +++++++- flask/testing.py | 2 +- flask/wrappers.py | 4 ++-- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index 88bf40e8..47bf3cab 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -14,7 +14,8 @@ __version__ = '0.8-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. -from werkzeug import abort, redirect +from werkzeug.exceptions import abort +from werkzeug.utils import redirect from jinja2 import Markup, escape from .app import Flask, Request, Response diff --git a/flask/app.py b/flask/app.py index 8ca74e30..b4e3d647 100644 --- a/flask/app.py +++ b/flask/app.py @@ -16,7 +16,7 @@ from threading import Lock from datetime import timedelta from itertools import chain -from werkzeug import ImmutableDict +from werkzeug.datastructures import ImmutableDict from werkzeug.routing import Map, Rule from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed @@ -551,7 +551,7 @@ class Flask(_PackageBoundObject): Werkzeug server. See :func:`werkzeug.run_simple` for more information. """ - from werkzeug import run_simple + from werkzeug.serving import run_simple if 'debug' in options: self.debug = options.pop('debug') options.setdefault('use_reloader', self.debug) @@ -1267,7 +1267,7 @@ class Flask(_PackageBoundObject): :func:`werkzeug.create_environ` for more information, this function accepts the same arguments). """ - from werkzeug import create_environ + from werkzeug.test import create_environ environ_overrides = kwargs.setdefault('environ_overrides', {}) if self.config.get('SERVER_NAME'): server_name = self.config.get('SERVER_NAME') diff --git a/flask/config.py b/flask/config.py index bb2d6e9e..06dd02e2 100644 --- a/flask/config.py +++ b/flask/config.py @@ -15,7 +15,7 @@ import imp import os import errno -from werkzeug import import_string +from werkzeug.utils import import_string class ConfigAttribute(object): diff --git a/flask/globals.py b/flask/globals.py index 84714105..34099263 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -11,7 +11,7 @@ """ from functools import partial -from werkzeug import LocalStack, LocalProxy +from werkzeug.local import LocalStack, LocalProxy def _lookup_object(name): top = _request_ctx_stack.top diff --git a/flask/helpers.py b/flask/helpers.py index 2a841236..f44a7f64 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -36,9 +36,15 @@ except ImportError: json_available = False -from werkzeug import Headers, wrap_file +from werkzeug.datastructures import Headers from werkzeug.exceptions import NotFound +# this was moved in 0.7 +try: + from werkzeug.wsgi import wrap_file +except ImportError: + from werkzeug.utils import wrap_file + from jinja2 import FileSystemLoader from .globals import session, _request_ctx_stack, current_app, request diff --git a/flask/testing.py b/flask/testing.py index 84237336..06a2c016 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -from werkzeug import Client, EnvironBuilder +from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack diff --git a/flask/wrappers.py b/flask/wrappers.py index e592cc28..d5f6ed7d 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -9,8 +9,8 @@ :license: BSD, see LICENSE for more details. """ -from werkzeug import Request as RequestBase, Response as ResponseBase, \ - cached_property +from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase +from werkzeug.utils import cached_property from .helpers import json, _assert_have_json from .globals import _request_ctx_stack From 8d2daea327c2d0654004c2be9cb2eae074bbb80a Mon Sep 17 00:00:00 2001 From: Felix Hummel Date: Thu, 14 Jul 2011 21:18:53 +0200 Subject: [PATCH 0049/3143] be consistent with app.config['UPLOAD_FOLDER'] Signed-off-by: Armin Ronacher --- docs/patterns/fileuploads.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index ab49cedb..d237b107 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -28,6 +28,7 @@ bootstrapping code for our application:: ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) app = Flask(__name__) + app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER So first we need a couple of imports. Most should be straightforward, the :func:`werkzeug.secure_filename` is explained a little bit later. The @@ -58,7 +59,7 @@ the file and redirects the user to the URL for the uploaded file:: file = request.files['file'] if file and allowed_file(file.filename): filename = secure_filename(file.filename) - file.save(os.path.join(UPLOAD_FOLDER, filename)) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return redirect(url_for('uploaded_file', filename=filename)) return ''' @@ -116,7 +117,7 @@ older versions of Flask:: app.add_url_rule('/uploads/', 'uploaded_file', build_only=True) app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { - '/uploads': UPLOAD_FOLDER + '/uploads': app.config['UPLOAD_FOLDER'] }) If you now run the application everything should work as expected. From c9a2ad2b8d079c0ecca701f4c0b15390437c4843 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 16 Jul 2011 01:16:03 +0200 Subject: [PATCH 0050/3143] Fixed a bug in list_templates --- CHANGES | 8 ++++++++ flask/templating.py | 4 +++- tests/blueprintapp/__init__.py | 4 ++-- tests/blueprintapp/apps/admin/__init__.py | 4 +++- tests/blueprintapp/apps/frontend/__init__.py | 2 +- tests/flask_tests.py | 6 ++++++ 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index f9f3c82d..4f400d8e 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,14 @@ Relase date to be decided, codename to be chosen. - View functions can now opt out of getting the automatic OPTIONS implementation. +Version 0.7.3 +------------- + +Bugfix release, release date to be decided + +- Fixed the Jinja2 environment's list_templates method not returning the + correct names when blueprints or modules were involved. + Version 0.7.2 ------------- diff --git a/flask/templating.py b/flask/templating.py index 147f49d0..d38d3824 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -78,6 +78,8 @@ class DispatchingJinjaLoader(BaseLoader): pass for blueprint in self.app.blueprints.itervalues(): + if blueprint_is_module(blueprint): + continue loader = blueprint.jinja_loader if loader is not None: yield loader, template @@ -93,7 +95,7 @@ class DispatchingJinjaLoader(BaseLoader): if loader is not None: for template in loader.list_templates(): prefix = '' - if not blueprint_is_module(blueprint): + if blueprint_is_module(blueprint): prefix = name + '/' result.add(prefix + template) diff --git a/tests/blueprintapp/__init__.py b/tests/blueprintapp/__init__.py index fa76807c..2b8ef75d 100644 --- a/tests/blueprintapp/__init__.py +++ b/tests/blueprintapp/__init__.py @@ -1,7 +1,7 @@ from flask import Flask app = Flask(__name__) -from moduleapp.apps.admin import admin -from moduleapp.apps.frontend import frontend +from blueprintapp.apps.admin import admin +from blueprintapp.apps.frontend import frontend app.register_blueprint(admin) app.register_blueprint(frontend) diff --git a/tests/blueprintapp/apps/admin/__init__.py b/tests/blueprintapp/apps/admin/__init__.py index fe33e3e9..3f714d95 100644 --- a/tests/blueprintapp/apps/admin/__init__.py +++ b/tests/blueprintapp/apps/admin/__init__.py @@ -1,6 +1,8 @@ from flask import Blueprint, render_template -admin = Blueprint(__name__, url_prefix='/admin') +admin = Blueprint('admin', __name__, url_prefix='/admin', + template_folder='templates', + static_folder='static') @admin.route('/') diff --git a/tests/blueprintapp/apps/frontend/__init__.py b/tests/blueprintapp/apps/frontend/__init__.py index e98ff280..69c8666a 100644 --- a/tests/blueprintapp/apps/frontend/__init__.py +++ b/tests/blueprintapp/apps/frontend/__init__.py @@ -1,6 +1,6 @@ from flask import Blueprint, render_template -frontend = Blueprint(__name__) +frontend = Blueprint('frontend', __name__, template_folder='templates') @frontend.route('/') diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 095f4bad..7e00d73e 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1414,6 +1414,12 @@ class BlueprintTestCase(unittest.TestCase): with flask.Flask(__name__).test_request_context(): assert flask.render_template('nested/nested.txt') == 'I\'m nested' + def test_templates_list(self): + from blueprintapp import app + templates = sorted(app.jinja_env.list_templates()) + self.assertEqual(templates, ['admin/index.html', + 'frontend/index.html']) + def test_dotted_names(self): frontend = flask.Blueprint('myapp.frontend', __name__) backend = flask.Blueprint('myapp.backend', __name__) From 13cddba84f90d28899d1f0f7c2c9bcc901cee64c Mon Sep 17 00:00:00 2001 From: Ustun Ozgur Date: Mon, 18 Jul 2011 06:49:53 -0700 Subject: [PATCH 0051/3143] Typo: developped -> developed --- README | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README b/README index 56d57f88..5c0eb4f1 100644 --- a/README +++ b/README @@ -1,4 +1,3 @@ - // Flask // web development, one drop at a time @@ -8,7 +7,7 @@ Flask is a microframework for Python based on Werkzeug and Jinja2. It's intended for small scale applications - and was developped with best intentions in mind. + and was developed with best intentions in mind. ~ Is it ready? From 343e678900a9970857f37b53b5d2f069327cd828 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 18 Jul 2011 23:06:15 +0200 Subject: [PATCH 0052/3143] Fixed a typo in a docstring --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index b4e3d647..6a5a417b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -856,7 +856,7 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 One can now additionally also register custom exception types that do not necessarily have to be a subclass of the - :class:~`werkzeug.exceptions.HTTPException` class. + :class:`~werkzeug.exceptions.HTTPException` class. :param code: the code as integer for the handler """ From 290925e13348385cd3508d9ae52ea328ee9990d9 Mon Sep 17 00:00:00 2001 From: Dan Callahan Date: Tue, 19 Jul 2011 16:42:11 -0500 Subject: [PATCH 0053/3143] Minor documentation typo / grammar fixes --- docs/patterns/templateinheritance.rst | 2 +- flask/helpers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/templateinheritance.rst b/docs/patterns/templateinheritance.rst index 8a1a306d..70015ecc 100644 --- a/docs/patterns/templateinheritance.rst +++ b/docs/patterns/templateinheritance.rst @@ -38,7 +38,7 @@ document that you might use for a simple two-column page. It's the job of In this example, the ``{% block %}`` tags define four blocks that child templates -can fill in. All the `block` tag does is to tell the template engine that a +can fill in. All the `block` tag does is tell the template engine that a child template may override those portions of the template. Child Template diff --git a/flask/helpers.py b/flask/helpers.py index f44a7f64..dff79f68 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -421,7 +421,7 @@ def safe_join(directory, filename): :param directory: the base directory. :param filename: the untrusted filename relative to that directory. - :raises: :class:`~werkzeug.exceptions.NotFound` if the retsulting path + :raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path would fall out of `directory`. """ filename = posixpath.normpath(filename) From d5cfcfeba7313d80d7ac29adb8c54960cb594079 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 23 Jul 2011 11:13:30 -0700 Subject: [PATCH 0054/3143] Intersphinx link fixes. --- flask/app.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index b4e3d647..d6d95350 100644 --- a/flask/app.py +++ b/flask/app.py @@ -548,8 +548,9 @@ class Flask(_PackageBoundObject): to have the server available externally as well. :param port: the port of the webserver :param options: the options to be forwarded to the underlying - Werkzeug server. See :func:`werkzeug.run_simple` - for more information. + Werkzeug server. See + :func:`werkzeug.serving.run_simple` for more + information. """ from werkzeug.serving import run_simple if 'debug' in options: @@ -1264,7 +1265,7 @@ class Flask(_PackageBoundObject): def test_request_context(self, *args, **kwargs): """Creates a WSGI environment from the given values (see - :func:`werkzeug.create_environ` for more information, this + :func:`werkzeug.test.EnvironBuilder` for more information, this function accepts the same arguments). """ from werkzeug.test import create_environ From c240a1f78101c57591bd23f26a367d214d533eac Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 26 Jul 2011 16:32:36 -0400 Subject: [PATCH 0055/3143] Add code organization help to tutorial doc. As suggested by burhan on #pocoo today. --- docs/tutorial/dbcon.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index b19cb14c..99391a27 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -39,3 +39,19 @@ environments. That special :data:`~flask.g` object does some magic behind the scenes to ensure it does the right thing. Continue to :ref:`tutorial-views`. + +.. hint:: Where do I put this code? + + If you've been following along in this tutorial, you might be wondering + where to put the code from this step and the next. A logical place is to + group these module-level functions together, and put your new + ``before_request`` and ``teardown_request`` functions below your existing + ``init_db`` function (following the tutorial line-by-line). + + If you need a moment to find your bearings, take a look at how the `example + source`_ is organized. In Flask, you can put all of your application code + into a single Python module. You don't have to, and if your app :ref:`grows + larger `, it's a good idea not to. + +.. _example source: + http://github.com/mitsuhiko/flask/tree/master/examples/flaskr/ From 4d2c8181b4283e3e4d322739f49137e5553f8315 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jul 2011 22:34:18 +0200 Subject: [PATCH 0056/3143] Updated docs for streaming --- docs/patterns/index.rst | 1 + docs/patterns/streaming.rst | 70 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 docs/patterns/streaming.rst diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 4233808b..3a1e409c 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -35,3 +35,4 @@ Snippet Archives `_. lazyloading mongokit favicon + streaming diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst new file mode 100644 index 00000000..dbc3e921 --- /dev/null +++ b/docs/patterns/streaming.rst @@ -0,0 +1,70 @@ +Streaming Contents +================== + +Sometimes you want to send an enormous amount of data to the client, much +more than you want to keep in memory. When you are generating the data on +the fly though, how do you send that back to the client without the +roundtrip to the filesystem? + +The answer is by using generators and direct responses. + +Basic Usage +----------- + +This is a basic view function that generates a lot of CSV data on the fly. +The trick is to have an inner function that uses a generator to generate +data and to then invoke that function and pass it to a response object +that has the ``direct_passthrough`` flag set. This flag is used to inform +the system that data is generated on the fly and should be passed through +without buffering: + +.. sourcecode:: python + + from flask import Response + + @app.route('/large.csv') + def generate_large_csv(): + def generate(): + for row in iter_all_rows(): + yield ','.join(row) + '\n' + return Response(generate(), direct_passthrough=True, + mimetype='text/csv') + +Each ``yield`` expression is directly sent to the browser. Now though +that some WSGI middlewares might break streaming, so be careful there in +debug environments with profilers and other things you might have enabled. + +Streaming from Templates +------------------------ + +The Jinja2 template engine also supports rendering templates piece by +piece. This functionality is not directly exposed by Flask because it is +quite uncommon, but you can easily do it yourself: + +.. sourcecode:: python + + from flask import Response + + def stream_template(template_name, **context): + app.update_template_context(context) + t = app.jinja_env.get_template(template_name) + rv = t.stream(context) + rv.enable_buffering(5) + return rv + + @app.route('/my-large-page.html') + def render_large_template(): + rows = iter_all_rows() + return Response(stream_template('the_template.html', rows=rows), + direct_passthrough=True) + +The trick here is to get the template object from the Jinja2 environment +on the application and to call :meth:`~jinja2.Template.stream` instead of +:meth:`~jinja2.Template.render` which returns a stream object instead of a +string. Since we're bypassing the Flask template render functions and +using the template object itself we have to make sure to update the render +context ourselves by calling :meth:`~flask.Flask.update_template_context`. +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. From 029eebe2f57ed460c3e72d41422f3ffd01f0d4c1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 27 Jul 2011 22:34:56 +0200 Subject: [PATCH 0057/3143] No need for the directive here. --- docs/patterns/streaming.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index dbc3e921..1998799f 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -16,9 +16,7 @@ The trick is to have an inner function that uses a generator to generate data and to then invoke that function and pass it to a response object that has the ``direct_passthrough`` flag set. This flag is used to inform the system that data is generated on the fly and should be passed through -without buffering: - -.. sourcecode:: python +without buffering:: from flask import Response @@ -39,9 +37,7 @@ Streaming from Templates The Jinja2 template engine also supports rendering templates piece by piece. This functionality is not directly exposed by Flask because it is -quite uncommon, but you can easily do it yourself: - -.. sourcecode:: python +quite uncommon, but you can easily do it yourself:: from flask import Response From a7b85c9f15cccc0f4bd9560ecf7c11af8d3b7b53 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 29 Jul 2011 09:54:50 -0300 Subject: [PATCH 0058/3143] Consistent quotes --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 5eaaf9c5..baca5050 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -18,7 +18,7 @@ A minimal Flask application looks something like this:: @app.route('/') def hello_world(): - return "Hello World!" + return 'Hello World!' if __name__ == '__main__': app.run() From 153d727d1f8396b5486dccb48c033e109cba93d6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 30 Jul 2011 02:07:31 +0200 Subject: [PATCH 0059/3143] Fixed the docs for the request object. They were incomplete --- docs/api.rst | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index ddeed4a6..ff7e597a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,20 +29,7 @@ Incoming Request Data --------------------- .. autoclass:: Request - -.. class:: request - - To access incoming request data, you can use the global `request` - object. Flask parses incoming request data for you and gives you - access to it through that global object. Internally Flask makes - sure that you always get the correct data for the active thread if you - are in a multithreaded environment. - - This is a proxy. See :ref:`notes-on-proxies` for more information. - - The request object is an instance of a :class:`~werkzeug.wrappers.Request` - subclass and provides all of the attributes Werkzeug defines. This - just shows a quick overview of the most important ones. + :members: .. attribute:: form @@ -134,6 +121,21 @@ Incoming Request Data the incoming data was `application/json`. This requires Python 2.6 or an installed version of simplejson. +.. class:: request + + To access incoming request data, you can use the global `request` + object. Flask parses incoming request data for you and gives you + access to it through that global object. Internally Flask makes + sure that you always get the correct data for the active thread if you + are in a multithreaded environment. + + This is a proxy. See :ref:`notes-on-proxies` for more information. + + The request object is an instance of a :class:`~werkzeug.wrappers.Request` + subclass and provides all of the attributes Werkzeug defines. This + just shows a quick overview of the most important ones. + + Response Objects ---------------- From eba2f70c6b95d9d61b5854feea64f0ba1fb620d2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 30 Jul 2011 02:11:30 +0200 Subject: [PATCH 0060/3143] Clarified a sentence that no longer made sense with the new docs on request objects. --- flask/wrappers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flask/wrappers.py b/flask/wrappers.py index d5f6ed7d..37d972d1 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -23,6 +23,10 @@ class Request(RequestBase): It is what ends up as :class:`~flask.request`. If you want to replace the request object used you can subclass this and set :attr:`~flask.Flask.request_class` to your subclass. + + The request object is a :class:`~werkzeug.wrappers.Request` subclass and + provides all of the attributes Werkzeug defines plus a few Flask + specific ones. """ #: the internal URL rule that matched the request. This can be From c726e5fee0ce7ec3a0a0aab00cbd18b26c31be9d Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sat, 30 Jul 2011 11:02:36 -0400 Subject: [PATCH 0061/3143] Fix lighttpd config, from giskard on #pocoo. --- 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 0b5d887c..6dace1a8 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -62,7 +62,7 @@ A basic FastCGI configuration for lighttpd looks like that:: "socket" => "/tmp/yourapplication-fcgi.sock", "bin-path" => "/var/www/yourapplication/yourapplication.fcgi", "check-local" => "disable", - "max-procs" -> 1 + "max-procs" => 1 )) ) From fafcc02f261e90a006efd5cc4dfa876531af9594 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 4 Aug 2011 16:44:42 +0200 Subject: [PATCH 0062/3143] Added a testcase for 404 errors caused by the routing system --- tests/flask_tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 7e00d73e..fb3e0e52 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -564,6 +564,18 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert rv.status_code == 500 assert 'internal server error' == rv.data + def test_before_request_and_routing_errors(self): + app = flask.Flask(__name__) + @app.before_request + def attach_something(): + flask.g.something = 'value' + @app.errorhandler(404) + def return_something(error): + return flask.g.something, 404 + rv = app.test_client().get('/') + assert rv.status_code == 404 + assert rv.data == 'value' + def test_user_error_handling(self): class MyException(Exception): pass From 5304a15adc2ff1f19b889a40d039008404b25a61 Mon Sep 17 00:00:00 2001 From: Brandon Stafford Date: Thu, 4 Aug 2011 22:31:11 -0300 Subject: [PATCH 0063/3143] Add runserver.py filename to match earlier section of example --- docs/patterns/packages.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 4cd9dd27..79fd2c58 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -77,6 +77,7 @@ And this is what `views.py` would look like:: You should then end up with something like that:: /yourapplication + /runserver.py /yourapplication /__init__.py /views.py From 7155f11a723695352323d484610b9a1c6798eb69 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 12:35:41 +0200 Subject: [PATCH 0064/3143] Added HTTP exception trapping. This should fix #294 --- CHANGES | 2 ++ docs/config.rst | 20 ++++++++++++++++++++ flask/app.py | 36 +++++++++++++++++++++++++++++------- tests/flask_tests.py | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 4f400d8e..ae9cbdb9 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,8 @@ Relase date to be decided, codename to be chosen. - Empty session cookies are now deleted properly automatically. - View functions can now opt out of getting the automatic OPTIONS implementation. +- HTTP exceptions and Bad Request Key Errors can now be trapped so that they + show up normally in the traceback. Version 0.7.3 ------------- diff --git a/docs/config.rst b/docs/config.rst index 2487e8b7..4995ee84 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -81,6 +81,23 @@ 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. +``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will + not execute the error handlers of HTTP + exceptions but instead treat the + exception like any other and bubble it + through the exception stack. This is + helpful for hairy debugging situations + where you have to find out where an HTTP + exception is coming from. +``TRAP_BAD_REQUEST_KEY_ERRORS`` Werkzeug's internal data structures that + deal with request specific data will + raise special key errors that are also + bad request exceptions. By default + these will be converted into 400 + responses which however can make + debugging some issues harder. If this + config is set to ``True`` you will get + a regular traceback instead. ================================= ========================================= .. admonition:: More on ``SERVER_NAME`` @@ -114,6 +131,9 @@ The following configuration values are used internally by Flask: .. versionadded:: 0.7 ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` +.. versionadded:: 0.8 + ``TRAP_BAD_REQUEST_KEY_ERRORS``, ``TRAP_HTTP_EXCEPTIONS`` + Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 5c448144..359a233d 100644 --- a/flask/app.py +++ b/flask/app.py @@ -19,7 +19,7 @@ from itertools import chain from werkzeug.datastructures import ImmutableDict from werkzeug.routing import Map, Rule from werkzeug.exceptions import HTTPException, InternalServerError, \ - MethodNotAllowed + MethodNotAllowed, BadRequest from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ locked_cached_property, _tojson_filter, _endpoint_from_view_func @@ -197,7 +197,9 @@ class Flask(_PackageBoundObject): 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'SERVER_NAME': None, - 'MAX_CONTENT_LENGTH': None + 'MAX_CONTENT_LENGTH': None, + 'TRAP_BAD_REQUEST_KEY_ERRORS': False, + 'TRAP_HTTP_EXCEPTIONS': False }) #: The rule object to use for URL rules created. This is used by @@ -983,6 +985,24 @@ class Flask(_PackageBoundObject): return e return handler(e) + def trap_http_exception(self, e): + """Checks if an HTTP exception should be trapped or not. By default + this will return `False` for all exceptions except for a bad request + key error if ``TRAP_BAD_REQUEST_KEY_ERRORS`` is set to `True`. It + also returns `True` if ``TRAP_HTTP_EXCEPTIONS`` is set to `True`. + + This is called for all HTTP exceptions raised by a view function. + If it returns `True` for any exception the error handler for this + exception is not called and it shows up as regular exception in the + traceback. This is helpful for debugging implicitly raised HTTP + exceptions. + """ + if self.config['TRAP_HTTP_EXCEPTIONS']: + return True + if self.config['TRAP_BAD_REQUEST_KEY_ERRORS']: + return isinstance(e, BadRequest) and isinstance(e, LookupError) + return False + def handle_user_exception(self, e): """This method is called whenever an exception occurs that should be handled. A special case are @@ -993,14 +1013,16 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 """ - # ensure not to trash sys.exc_info() at that point in case someone - # wants the traceback preserved in handle_http_exception. - if isinstance(e, HTTPException): - return self.handle_http_exception(e) - exc_type, exc_value, tb = sys.exc_info() assert exc_value is e + # ensure not to trash sys.exc_info() at that point in case someone + # wants the traceback preserved in handle_http_exception. Of course + # we cannot prevent users from trashing it themselves in a custom + # trap_http_exception method so that's their fault then. + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + blueprint_handlers = () handlers = self.error_handler_spec.get(request.blueprint) if handlers is not None: diff --git a/tests/flask_tests.py b/tests/flask_tests.py index fb3e0e52..dbd38e12 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -23,7 +23,7 @@ from contextlib import contextmanager from functools import update_wrapper from datetime import datetime from werkzeug import parse_date, parse_options_header -from werkzeug.exceptions import NotFound +from werkzeug.exceptions import NotFound, BadRequest from werkzeug.http import parse_set_header from jinja2 import TemplateNotFound from cStringIO import StringIO @@ -592,6 +592,40 @@ class BasicFunctionalityTestCase(unittest.TestCase): c = app.test_client() assert c.get('/').data == '42' + def test_trapping_of_bad_request_key_errors(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/fail') + def fail(): + flask.request.form['missing_key'] + c = app.test_client() + assert c.get('/fail').status_code == 400 + + app.config['TRAP_BAD_REQUEST_KEY_ERRORS'] = True + c = app.test_client() + try: + c.get('/fail') + except KeyError, e: + assert isinstance(e, BadRequest) + else: + self.fail('Expected exception') + + def test_trapping_of_all_http_exceptions(self): + app = flask.Flask(__name__) + app.testing = True + app.config['TRAP_HTTP_EXCEPTIONS'] = True + @app.route('/fail') + def fail(): + flask.abort(404) + + c = app.test_client() + try: + c.get('/fail') + except NotFound, e: + pass + else: + self.fail('Expected exception') + def test_teardown_on_pop(self): buffer = [] app = flask.Flask(__name__) From afe5d3cbd44056893aa8c11f4bf17496cee92022 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 12:37:57 +0200 Subject: [PATCH 0065/3143] Added a missing versionadded --- flask/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/app.py b/flask/app.py index 359a233d..1eed507d 100644 --- a/flask/app.py +++ b/flask/app.py @@ -996,6 +996,8 @@ class Flask(_PackageBoundObject): exception is not called and it shows up as regular exception in the traceback. This is helpful for debugging implicitly raised HTTP exceptions. + + .. versionadded:: 0.8 """ if self.config['TRAP_HTTP_EXCEPTIONS']: return True From a69b437af705dc8fa4e1b38b86ddad77d6dedb54 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 12:40:44 +0200 Subject: [PATCH 0066/3143] Cleanup for the json property --- flask/wrappers.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index 37d972d1..57c62ce1 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -96,11 +96,8 @@ class Request(RequestBase): if self.mimetype == 'application/json': request_charset = self.mimetype_params.get('charset') if request_charset is not None: - j = json.loads(self.data, encoding=request_charset ) - else: - j = json.loads(self.data) - - return j + return json.loads(self.data, encoding=request_charset) + return json.loads(self.data) class Response(ResponseBase): From 5f367e879db5e887f68d2c026fe25a0c62cc3c33 Mon Sep 17 00:00:00 2001 From: Brandon Stafford Date: Fri, 5 Aug 2011 11:12:21 -0300 Subject: [PATCH 0067/3143] Fix typos. --- docs/blueprints.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/blueprints.rst b/docs/blueprints.rst index c71d11bb..9422fd02 100644 --- a/docs/blueprints.rst +++ b/docs/blueprints.rst @@ -125,16 +125,16 @@ Blueprint Resource Folder ````````````````````````` Like for regular applications, blueprints are considered to be contained -in a folder. While multiple blueprints can origin from the same folder, +in a folder. While multiple blueprints can originate from the same folder, it does not have to be the case and it's usually not recommended. -The folder is infered from the second argument to :class:`Blueprint` which -is ususally `__name__`. This argument specifies what logical Python +The folder is inferred from the second argument to :class:`Blueprint` which +is usually `__name__`. This argument specifies what logical Python module or package corresponds to the blueprint. If it points to an actual Python package that package (which is a folder on the filesystem) is the resource folder. If it's a module, the package the module is contained in will be the resource folder. You can access the -:attr:`Blueprint.root_path` property to see what's the resource folder:: +:attr:`Blueprint.root_path` property to see what the resource folder is:: >>> simple_page.root_path '/Users/username/TestProject/yourapplication' From 2e022cb2727cfa99355261f2c18d1e0fc56e82ee Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 16:43:42 +0200 Subject: [PATCH 0068/3143] Added debughelpers. Flask will now tell you if you forget enctype --- CHANGES | 2 ++ flask/debughelpers.py | 52 +++++++++++++++++++++++++++++++++++++++++++ flask/wrappers.py | 11 +++++++++ 3 files changed, 65 insertions(+) create mode 100644 flask/debughelpers.py diff --git a/CHANGES b/CHANGES index ae9cbdb9..b7be2c52 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Relase date to be decided, codename to be chosen. OPTIONS implementation. - HTTP exceptions and Bad Request Key Errors can now be trapped so that they show up normally in the traceback. +- Flask in debug mode is now detecting some common problems and tries to + warn you about them. Version 0.7.3 ------------- diff --git a/flask/debughelpers.py b/flask/debughelpers.py new file mode 100644 index 00000000..dd1fffe9 --- /dev/null +++ b/flask/debughelpers.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" + flask.debughelpers + ~~~~~~~~~~~~~~~~~~ + + Various helpers to make the development experience better. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + + +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. + """ + + def __init__(self, request, key): + form_matches = request.form.getlist(key) + buf = ['You tried to access the file "%s" in the request.files ' + 'dictionary but it does not exist. The mimetype for the request ' + 'is "%s" instead of "multipart/form-data" which means that no ' + 'files were transmitted. To fix this error you most likely have ' + 'to provide enctype="multipart/form-data" in your form.' % + (key, request.mimetype)] + if form_matches: + buf.append('\n\nThe browser instead most likely submitted the ' + 'filenames in the form. This was submitted: %s' % + ', '.join('"%s"' % x for x in form_matches)) + self.msg = ''.join(buf) + + def __str__(self): + return self.msg + + +def make_enctype_error_multidict(request): + """Since Flask 0.8 we're monkeypatching the files object in case a + request is detected that does not use multipart form data but the files + object is accessed. + """ + oldcls = request.files.__class__ + class newcls(oldcls): + def __getitem__(self, key): + try: + return oldcls.__getitem__(self, key) + except KeyError, e: + if key not in request.form: + raise + raise DebugFilesKeyError(request, key) + newcls.__name__ = oldcls.__name__ + newcls.__module__ = oldcls.__module__ + request.files.__class__ = newcls diff --git a/flask/wrappers.py b/flask/wrappers.py index 57c62ce1..06f60beb 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -12,6 +12,7 @@ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase from werkzeug.utils import cached_property +from .debughelpers import make_enctype_error_multidict from .helpers import json, _assert_have_json from .globals import _request_ctx_stack @@ -99,6 +100,16 @@ class Request(RequestBase): return json.loads(self.data, encoding=request_charset) return json.loads(self.data) + def _load_form_data(self): + RequestBase._load_form_data(self) + + # in debug mode we're replacing the files multidict with an ad-hoc + # subclass that raises a different error for key errors. + ctx = _request_ctx_stack.top + if ctx is not None and ctx.app.debug and \ + self.mimetype != 'multipart/form-data': + make_enctype_error_multidict(self) + class Response(ResponseBase): """The response object that is used by default in Flask. Works like the From 17a836d49c8d496d50b726132e58749cf2741dd6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 16:47:12 +0200 Subject: [PATCH 0069/3143] Better wording --- flask/debughelpers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index dd1fffe9..9ba81e5e 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -20,13 +20,13 @@ class DebugFilesKeyError(KeyError, AssertionError): buf = ['You tried to access the file "%s" in the request.files ' 'dictionary but it does not exist. The mimetype for the request ' 'is "%s" instead of "multipart/form-data" which means that no ' - 'files were transmitted. To fix this error you most likely have ' - 'to provide enctype="multipart/form-data" in your form.' % + 'file contents were transmitted. To fix this error you should ' + 'provide enctype="multipart/form-data" in your form.' % (key, request.mimetype)] if form_matches: - buf.append('\n\nThe browser instead most likely submitted the ' - 'filenames in the form. This was submitted: %s' % - ', '.join('"%s"' % x for x in form_matches)) + buf.append('\n\nThe browser instead some file names. This was ' + 'submitted: %s' % ', '.join('"%s"' % x + for x in form_matches)) self.msg = ''.join(buf) def __str__(self): From 23c1dd8db2a8dc028516a57b6dca47733bb82173 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 16:50:02 +0200 Subject: [PATCH 0070/3143] Wording fail --- flask/debughelpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 9ba81e5e..8f167e7e 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -24,8 +24,8 @@ class DebugFilesKeyError(KeyError, AssertionError): 'provide enctype="multipart/form-data" in your form.' % (key, request.mimetype)] if form_matches: - buf.append('\n\nThe browser instead some file names. This was ' - 'submitted: %s' % ', '.join('"%s"' % x + 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) From 3400b78db23fbd305cb11368946be1bf280e2696 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 16:51:45 +0200 Subject: [PATCH 0071/3143] Only provide that monkeypatch if all files are missing --- flask/wrappers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index 06f60beb..8ea4b840 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -107,7 +107,7 @@ class Request(RequestBase): # subclass that raises a different error for key errors. ctx = _request_ctx_stack.top if ctx is not None and ctx.app.debug and \ - self.mimetype != 'multipart/form-data': + self.mimetype != 'multipart/form-data' and not self.files: make_enctype_error_multidict(self) From f3db68c8cee4f48fbc078e1f5a73bb025b425f91 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 16:56:43 +0200 Subject: [PATCH 0072/3143] Added testcase for the debug behavior and explicit encoding --- flask/debughelpers.py | 2 +- tests/flask_tests.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 8f167e7e..e3bdf76f 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -27,7 +27,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) + self.msg = ''.join(buf).encode('utf-8') def __str__(self): return self.msg diff --git a/tests/flask_tests.py b/tests/flask_tests.py index dbd38e12..3789cd97 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -626,6 +626,26 @@ class BasicFunctionalityTestCase(unittest.TestCase): else: self.fail('Expected exception') + def test_enctype_debug_helper(self): + from flask.debughelpers import DebugFilesKeyError + app = flask.Flask(__name__) + app.debug = True + @app.route('/fail', methods=['POST']) + def index(): + return flask.request.files['foo'].filename + + # with statement is important because we leave an exception on the + # stack otherwise and we want to ensure that this is not the case + # to not negatively affect other tests. + with app.test_client() as c: + try: + c.post('/fail', data={'foo': 'index.txt'}) + except DebugFilesKeyError, e: + assert 'no file contents were transmitted' in str(e) + assert 'This was submitted: "index.txt"' in str(e) + else: + self.fail('Expected exception') + def test_teardown_on_pop(self): buffer = [] app = flask.Flask(__name__) From ac99cd33a827f0694d718dae5f2699430263ed7e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 5 Aug 2011 21:54:12 +0200 Subject: [PATCH 0073/3143] Better internal method name --- flask/debughelpers.py | 2 +- flask/wrappers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index e3bdf76f..7eae61c5 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -33,7 +33,7 @@ class DebugFilesKeyError(KeyError, AssertionError): return self.msg -def make_enctype_error_multidict(request): +def attach_enctype_error_multidict(request): """Since Flask 0.8 we're monkeypatching the files object in case a request is detected that does not use multipart form data but the files object is accessed. diff --git a/flask/wrappers.py b/flask/wrappers.py index 8ea4b840..73169799 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -12,7 +12,7 @@ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase from werkzeug.utils import cached_property -from .debughelpers import make_enctype_error_multidict +from .debughelpers import attach_enctype_error_multidict from .helpers import json, _assert_have_json from .globals import _request_ctx_stack @@ -108,7 +108,7 @@ class Request(RequestBase): ctx = _request_ctx_stack.top if ctx is not None and ctx.app.debug and \ self.mimetype != 'multipart/form-data' and not self.files: - make_enctype_error_multidict(self) + attach_enctype_error_multidict(self) class Response(ResponseBase): From 5ca17c86c088eb79a84b97e913e3122d917d042f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 01:51:02 +0200 Subject: [PATCH 0074/3143] Added a missing colon --- docs/patterns/sqlite3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index d0ec5a27..bc471f66 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -34,7 +34,7 @@ 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:: - with app.test_request_context() + with app.test_request_context(): app.preprocess_request() # now you can use the g.db object From 5500986971b28f270a27db633acf19984eee609e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 02:30:34 +0200 Subject: [PATCH 0075/3143] Flask in debug mode will now complain if views are attached after the first view was handled. --- CHANGES | 3 +++ flask/app.py | 35 +++++++++++++++++++++++++++++++++++ tests/flask_tests.py | 22 ++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/CHANGES b/CHANGES index b7be2c52..e4300363 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,9 @@ Relase date to be decided, codename to be chosen. show up normally in the traceback. - Flask in debug mode is now detecting some common problems and tries to warn you about them. +- Flask in debug mode will now complain with an assertion error if a view + was attached after the first request was handled. This gives earlier + feedback when users forget to import view code ahead of time. Version 0.7.3 ------------- diff --git a/flask/app.py b/flask/app.py index 1eed507d..df147863 100644 --- a/flask/app.py +++ b/flask/app.py @@ -15,6 +15,7 @@ import sys from threading import Lock from datetime import timedelta from itertools import chain +from functools import update_wrapper from werkzeug.datastructures import ImmutableDict from werkzeug.routing import Map, Rule @@ -38,6 +39,23 @@ from .signals import request_started, request_finished, got_request_exception, \ _logger_lock = Lock() +def setupmethod(f): + """Wraps a method so that it performs a check in debug mode if the + first request was already handled. + """ + def wrapper_func(self, *args, **kwargs): + if self.debug and self._got_first_request: + raise AssertionError('A setup function was called after the ' + 'first request was handled. This usually indicates a bug ' + 'in the application where a module was not imported ' + 'and decorators or other functionality was called too late.\n' + 'To fix this make sure to import all your view modules, ' + 'database models and everything related at a central place ' + 'before the application starts serving requests.') + return f(self, *args, **kwargs) + return update_wrapper(wrapper_func, f) + + class Flask(_PackageBoundObject): """The flask object implements a WSGI application and acts as the central object. It is passed the name of the module or package of the @@ -365,6 +383,10 @@ class Flask(_PackageBoundObject): #: app.url_map.converters['list'] = ListConverter self.url_map = Map() + # tracks internally if the application already handled at least one + # request. + self._got_first_request = False + # register the static folder for the application. Do that even # if the folder does not exist. First of all it might be created # while the server is running (usually happens during development) @@ -642,6 +664,7 @@ class Flask(_PackageBoundObject): self.register_blueprint(module, **options) + @setupmethod def register_blueprint(self, blueprint, **options): """Registers a blueprint on the application. @@ -659,6 +682,7 @@ class Flask(_PackageBoundObject): first_registration = True blueprint.register(self, options, first_registration) + @setupmethod def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """Connects a URL rule. Works exactly like the :meth:`route` decorator. If a view_func is provided it will be registered with the @@ -812,6 +836,7 @@ class Flask(_PackageBoundObject): return f return decorator + @setupmethod def endpoint(self, endpoint): """A decorator to register a function as an endpoint. Example:: @@ -827,6 +852,7 @@ class Flask(_PackageBoundObject): return f return decorator + @setupmethod def errorhandler(self, code_or_exception): """A decorator that is used to register a function give a given error code. Example:: @@ -877,6 +903,7 @@ class Flask(_PackageBoundObject): """ self._register_error_handler(None, code_or_exception, f) + @setupmethod def _register_error_handler(self, key, code_or_exception, f): if isinstance(code_or_exception, HTTPException): code_or_exception = code_or_exception.code @@ -889,6 +916,7 @@ class Flask(_PackageBoundObject): self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \ .append((code_or_exception, f)) + @setupmethod def template_filter(self, name=None): """A decorator that is used to register custom template filter. You can specify a name for the filter, otherwise the function @@ -906,11 +934,13 @@ class Flask(_PackageBoundObject): return f return decorator + @setupmethod def before_request(self, f): """Registers a function to run before each request.""" self.before_request_funcs.setdefault(None, []).append(f) return f + @setupmethod def after_request(self, f): """Register a function to be run after each request. Your function must take one parameter, a :attr:`response_class` object and return @@ -922,6 +952,7 @@ class Flask(_PackageBoundObject): self.after_request_funcs.setdefault(None, []).append(f) return f + @setupmethod def teardown_request(self, f): """Register a function to be run at the end of each request, regardless of whether there was an exception or not. These functions @@ -948,11 +979,13 @@ class Flask(_PackageBoundObject): self.teardown_request_funcs.setdefault(None, []).append(f) return f + @setupmethod def context_processor(self, f): """Registers a template context processor function.""" self.template_context_processors[None].append(f) return f + @setupmethod def url_value_preprocessor(self, f): """Registers a function as URL value preprocessor for all view functions of the application. It's called before the view functions @@ -961,6 +994,7 @@ class Flask(_PackageBoundObject): self.url_value_preprocessors.setdefault(None, []).append(f) return f + @setupmethod def url_defaults(self, f): """Callback function for URL defaults for all view functions of the application. It's called with the endpoint and values and should @@ -1097,6 +1131,7 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 """ + self._got_first_request = True try: request_started.send(self) rv = self.preprocess_request() diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 3789cd97..59149286 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -944,6 +944,28 @@ class BasicFunctionalityTestCase(unittest.TestCase): self.assertEqual(c.get('/de/about').data, '/foo') self.assertEqual(c.get('/foo').data, '/en/about') + def test_debug_mode_complains_after_first_request(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/') + def index(): + return 'Awesome' + self.assertEqual(app.test_client().get('/').data, 'Awesome') + try: + @app.route('/foo') + def broken(): + return 'Meh' + except AssertionError, e: + self.assert_('A setup function was called' in str(e)) + else: + self.fail('Expected exception') + + app.debug = False + @app.route('/foo') + def working(): + return 'Meh' + self.assertEqual(app.test_client().get('/foo').data, 'Meh') + class JSONTestCase(unittest.TestCase): From 02a131746074203dc8d747308f4d2c7ece8aa743 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 12:43:38 +0200 Subject: [PATCH 0076/3143] Added the ability to trigger functions before the first request to the application --- CHANGES | 2 ++ flask/app.py | 52 ++++++++++++++++++++++++++++++++++++++++++-- flask/blueprints.py | 7 ++++++ tests/flask_tests.py | 15 +++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e4300363..cc8198d2 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Relase date to be decided, codename to be chosen. - Flask in debug mode will now complain with an assertion error if a view was attached after the first request was handled. This gives earlier feedback when users forget to import view code ahead of time. +- Added the ability to register callbacks that are only triggered once at + the beginning of the first request. (:meth:`Flask.before_first_request`) Version 0.7.3 ------------- diff --git a/flask/app.py b/flask/app.py index df147863..d299b4fb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -291,6 +291,13 @@ class Flask(_PackageBoundObject): #: function here, use the :meth:`before_request` decorator. self.before_request_funcs = {} + #: A lists of functions that should be called at the beginning of the + #: first request to this instance. To register a function here, use + #: the :meth:`before_first_request` decorator. + #: + #: .. versionadded:: 0.8 + self.before_first_request_funcs = [] + #: A dictionary with lists of functions that should be called after #: each request. The key of the dictionary is the name of the blueprint #: this function is active for, `None` for all requests. This can for @@ -386,6 +393,7 @@ class Flask(_PackageBoundObject): # tracks internally if the application already handled at least one # request. self._got_first_request = False + self._before_request_lock = Lock() # register the static folder for the application. Do that even # if the folder does not exist. First of all it might be created @@ -474,6 +482,15 @@ class Flask(_PackageBoundObject): return rv + @property + def got_first_request(self): + """This attribute is set to `True` if the application started + handling the first request. + + .. versionadded:: 0.8 + """ + return self._got_first_request + def create_jinja_environment(self): """Creates the Jinja2 environment based on :attr:`jinja_options` and :meth:`select_jinja_autoescape`. Since 0.7 this also adds @@ -581,7 +598,13 @@ class Flask(_PackageBoundObject): self.debug = options.pop('debug') options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) - return run_simple(host, port, self, **options) + try: + run_simple(host, port, self, **options) + finally: + # reset the first request information if the development server + # resetted normally. This makes it possible to restart the server + # without reloader and that stuff from an interactive shell. + self._got_first_request = False def test_client(self, use_cookies=True): """Creates a test client for this application. For information @@ -940,6 +963,15 @@ class Flask(_PackageBoundObject): self.before_request_funcs.setdefault(None, []).append(f) return f + @setupmethod + def before_first_request(self, f): + """Registers a function to be run before the first request to this + instance of the application. + + .. versionadded:: 0.8 + """ + self.before_first_request_funcs.append(f) + @setupmethod def after_request(self, f): """Register a function to be run after each request. Your function @@ -1131,7 +1163,7 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 """ - self._got_first_request = True + self.try_trigger_before_first_request_functions() try: request_started.send(self) rv = self.preprocess_request() @@ -1144,6 +1176,22 @@ class Flask(_PackageBoundObject): request_finished.send(self, response=response) return response + def try_trigger_before_first_request_functions(self): + """Called before each request and will ensure that it triggers + the :attr:`before_first_request_funcs` and only exactly once per + application instance (which means process usually). + + .. versionadded:: 0.8 + """ + if self._got_first_request: + return + with self._before_request_lock: + if self._got_first_request: + return + self._got_first_request = True + for func in self.before_first_request_funcs: + func() + def make_default_options_response(self): """This method is called to create the default `OPTIONS` response. This can be changed through subclassing to change the default diff --git a/flask/blueprints.py b/flask/blueprints.py index 0c3a7d0f..075961ab 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -199,6 +199,13 @@ class Blueprint(_PackageBoundObject): .setdefault(None, []).append(f)) return f + def before_app_first_request(self, f): + """Like :meth:`Flask.before_first_request`. Such a function is + executed before the first request to the application. + """ + self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) + return f + def after_request(self, f): """Like :meth:`Flask.after_request` but for a blueprint. This function is only executed after each request that is handled by a function of diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 59149286..467381eb 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -950,6 +950,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): @app.route('/') def index(): return 'Awesome' + self.assert_(not app.got_first_request) self.assertEqual(app.test_client().get('/').data, 'Awesome') try: @app.route('/foo') @@ -965,6 +966,20 @@ class BasicFunctionalityTestCase(unittest.TestCase): def working(): return 'Meh' self.assertEqual(app.test_client().get('/foo').data, 'Meh') + self.assert_(app.got_first_request) + + def test_before_first_request_functions(self): + got = [] + app = flask.Flask(__name__) + @app.before_first_request + def foo(): + got.append(42) + c = app.test_client() + c.get('/') + self.assertEqual(got, [42]) + c.get('/') + self.assertEqual(got, [42]) + self.assert_(app.got_first_request) class JSONTestCase(unittest.TestCase): From ae00f6d149c353e1ecb683ccbf01be221324a646 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 14:15:33 +0200 Subject: [PATCH 0077/3143] Tweaked autodoc to skip :internal: methods. --- docs/conf.py | 4 +++- docs/flaskdocext.py | 16 ++++++++++++++++ flask/app.py | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 docs/flaskdocext.py diff --git a/docs/conf.py b/docs/conf.py index 390361a6..16d7e670 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ import sys, os # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('_themes')) +sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- @@ -25,7 +26,8 @@ sys.path.append(os.path.abspath('_themes')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'flaskdocext'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/flaskdocext.py b/docs/flaskdocext.py new file mode 100644 index 00000000..db4cfd20 --- /dev/null +++ b/docs/flaskdocext.py @@ -0,0 +1,16 @@ +import re +import inspect + + +_internal_mark_re = re.compile(r'^\s*:internal:\s*$(?m)') + + +def skip_member(app, what, name, obj, skip, options): + docstring = inspect.getdoc(obj) + if skip: + return True + return _internal_mark_re.search(docstring or '') is not None + + +def setup(app): + app.connect('autodoc-skip-member', skip_member) diff --git a/flask/app.py b/flask/app.py index d299b4fb..8995c511 100644 --- a/flask/app.py +++ b/flask/app.py @@ -257,7 +257,7 @@ class Flask(_PackageBoundObject): #: to load a config from files. self.config = Config(self.root_path, self.default_config) - #: Prepare the deferred setup of the logger. + # Prepare the deferred setup of the logger. self._logger = None self.logger_name = self.import_name @@ -1181,7 +1181,7 @@ class Flask(_PackageBoundObject): the :attr:`before_first_request_funcs` and only exactly once per application instance (which means process usually). - .. versionadded:: 0.8 + :internal: """ if self._got_first_request: return From 13005d76b98979eeacb169765df3cc2a00b0bb4d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 15:23:57 +0200 Subject: [PATCH 0078/3143] Improved cookie documentation --- docs/quickstart.rst | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index baca5050..8bc544b0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -588,11 +588,38 @@ For some better examples, checkout the :ref:`uploading-files` pattern. Cookies ``````` -To access cookies you can use the :attr:`~flask.request.cookies` -attribute. Again this is a dictionary with all the cookies the client -transmits. If you want to use sessions, do not use the cookies directly -but instead use the :ref:`sessions` in Flask that add some security on top -of cookies for you. +To access cookies you can use the :attr:`~flask.Request.cookies` +attribute. To set cookies you can use the +:attr:`~flask.Response.set_cookie` method of response objects. The +:attr:`~flask.Request.cookies` attribute of request objects is a +dictionary with all the cookies the client transmits. If you want to use +sessions, do not use the cookies directly but instead use the +:ref:`sessions` in Flask that add some security on top of cookies for you. + +Reading cookies:: + + from flask import request + + @app.route('/') + def index(): + username = request.cookies.get('username') + # use cookies.get(key) instead of cookies[key] to not get a + # KeyError if the cookie is missing. + +Storing cookies:: + + from flask import make_response + + @app.route('/') + def index(): + resp = make_response(render_template(...)) + resp.set_cookie('username', 'the username') + return resp + +Note that cookies are set on response objects. Since you normally you +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. Redirects and Errors From 5b1653200047c7c257c924b66cf77f85247921a7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 16:21:58 +0200 Subject: [PATCH 0079/3143] Extended quickstart docs for the headers --- docs/quickstart.rst | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8bc544b0..690433db 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -621,6 +621,7 @@ 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. +For this also see :ref:`about-responses`. Redirects and Errors -------------------- @@ -658,6 +659,49 @@ Note the ``404`` after the :func:`~flask.render_template` call. This tells Flask that the status code of that page should be 404 which means not found. By default 200 is assumed which translates to: all went well. +.. _about-responses: + +About Responses +--------------- + +The return value from a view function is automatically converted into a +response object for you. If the return value is a string it's converted +into a response object with the string as response body, an ``200 OK`` +error code and a ``text/html`` mimetype. The logic that Flask applies to +converting return values into response objects is as follows: + +1. If a response object of the correct type is returned it's directly + 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. +4. If neither of that works, Flask will assume the return value is a + valid WSGI application and converts that into a response object. + +If you want to get hold of the resulting response object inside the view +you can use the :func:`~flask.make_response` function. + +Imagine you have a view like this: + +.. sourcecode:: python + + @app.errorhandler(404) + def not_found(error): + 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 +return it: + +.. sourcecode:: python + + @app.errorhandler(404) + def not_found(error): + resp = make_response(render_template('error.html'), 404) + resp.headers['X-Something'] = 'A value' + return resp + .. _sessions: Sessions From 505cd663cd75b5593a1c9db0eded5298bd67139a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 8 Aug 2011 01:26:39 +0200 Subject: [PATCH 0080/3143] Hopefully fixed release script date parsing thingy. This fixes #299 --- scripts/make-release.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/make-release.py b/scripts/make-release.py index 574cb75d..31f4fa9e 100644 --- a/scripts/make-release.py +++ b/scripts/make-release.py @@ -16,6 +16,8 @@ import re from datetime import datetime, date from subprocess import Popen, PIPE +_date_clean_re = re.compile(r'(\d+)(st|nd|rd|th)') + def parse_changelog(): with open('CHANGES') as f: @@ -52,8 +54,7 @@ def bump_version(version): def parse_date(string): - string = string.replace('th ', ' ').replace('nd ', ' ') \ - .replace('rd ', ' ').replace('st ', ' ') + string = _date_clean_re.sub(r'\1', string) return datetime.strptime(string, '%B %d %Y') From acac64e36a046cebae07bee7f3e8106b4a80288a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 8 Aug 2011 21:46:53 +0200 Subject: [PATCH 0081/3143] Don't only catch BadRequest key errors but all bad request errors. --- docs/config.rst | 14 ++++++++------ flask/app.py | 8 ++++---- tests/flask_tests.py | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 4995ee84..f50e3180 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -89,13 +89,15 @@ The following configuration values are used internally by Flask: helpful for hairy debugging situations where you have to find out where an HTTP exception is coming from. -``TRAP_BAD_REQUEST_KEY_ERRORS`` Werkzeug's internal data structures that +``TRAP_BAD_REQUEST_ERRORS`` Werkzeug's internal data structures that deal with request specific data will raise special key errors that are also - bad request exceptions. By default - these will be converted into 400 - responses which however can make - debugging some issues harder. If this + bad request exceptions. Likewise many + operations can implicitly fail with a + BadRequest exception for consistency. + Since it's nice for debugging to know + why exactly it failed this flag can be + used to debug those situations. If this config is set to ``True`` you will get a regular traceback instead. ================================= ========================================= @@ -132,7 +134,7 @@ The following configuration values are used internally by Flask: ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` .. versionadded:: 0.8 - ``TRAP_BAD_REQUEST_KEY_ERRORS``, ``TRAP_HTTP_EXCEPTIONS`` + ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 8995c511..009841f8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -216,7 +216,7 @@ class Flask(_PackageBoundObject): 'LOGGER_NAME': None, 'SERVER_NAME': None, 'MAX_CONTENT_LENGTH': None, - 'TRAP_BAD_REQUEST_KEY_ERRORS': False, + 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False }) @@ -1054,7 +1054,7 @@ class Flask(_PackageBoundObject): def trap_http_exception(self, e): """Checks if an HTTP exception should be trapped or not. By default this will return `False` for all exceptions except for a bad request - key error if ``TRAP_BAD_REQUEST_KEY_ERRORS`` is set to `True`. It + key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to `True`. It also returns `True` if ``TRAP_HTTP_EXCEPTIONS`` is set to `True`. This is called for all HTTP exceptions raised by a view function. @@ -1067,8 +1067,8 @@ class Flask(_PackageBoundObject): """ if self.config['TRAP_HTTP_EXCEPTIONS']: return True - if self.config['TRAP_BAD_REQUEST_KEY_ERRORS']: - return isinstance(e, BadRequest) and isinstance(e, LookupError) + if self.config['TRAP_BAD_REQUEST_ERRORS']: + return isinstance(e, BadRequest) return False def handle_user_exception(self, e): diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 467381eb..5e592710 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -601,7 +601,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): c = app.test_client() assert c.get('/fail').status_code == 400 - app.config['TRAP_BAD_REQUEST_KEY_ERRORS'] = True + app.config['TRAP_BAD_REQUEST_ERRORS'] = True c = app.test_client() try: c.get('/fail') From ce701319756abef4d09c585828a630b8fdef89fc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 8 Aug 2011 21:47:26 +0200 Subject: [PATCH 0082/3143] If JSON parsing fails it now issues a BadRequest exception. --- CHANGES | 5 ++++- docs/upgrading.rst | 7 +++++++ flask/wrappers.py | 19 ++++++++++++++++--- tests/flask_tests.py | 9 +++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index cc8198d2..c1d33869 100644 --- a/CHANGES +++ b/CHANGES @@ -14,7 +14,7 @@ Relase date to be decided, codename to be chosen. - Empty session cookies are now deleted properly automatically. - View functions can now opt out of getting the automatic OPTIONS implementation. -- HTTP exceptions and Bad Request Key Errors can now be trapped so that they +- HTTP exceptions and Bad Request errors can now be trapped so that they show up normally in the traceback. - Flask in debug mode is now detecting some common problems and tries to warn you about them. @@ -23,6 +23,9 @@ Relase date to be decided, codename to be chosen. feedback when users forget to import view code ahead of time. - Added the ability to register callbacks that are only triggered once at the beginning of the first request. (:meth:`Flask.before_first_request`) +- Malformed JSON data will now trigger a bad request HTTP exception instead + of a value error which usually would result in a 500 internal server + error if not handled. This is a backwards incompatible change. Version 0.7.3 ------------- diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 154b51a7..13b5be71 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -29,6 +29,13 @@ object. With that introduction we moved the implementation details for the session system into a new module called :mod:`flask.sessions`. If you used the previously undocumented session support we urge you to upgrade. +If invalid JSON data was submitted Flask will now raise a +:exc:`~werkzeug.exceptions.BadRequest` exception instead of letting the +default :exc:`ValueError` bubble up. This has the advantage that you no +longer have to handle that error to avoid an internal server error showing +up for the user. If you were catching this down explicitly in the past +as `ValueError` you will need to change this. + Version 0.7 ----------- diff --git a/flask/wrappers.py b/flask/wrappers.py index 73169799..2d3d53f7 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -10,6 +10,7 @@ """ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase +from werkzeug.exceptions import BadRequest from werkzeug.utils import cached_property from .debughelpers import attach_enctype_error_multidict @@ -96,9 +97,21 @@ class Request(RequestBase): _assert_have_json() if self.mimetype == 'application/json': request_charset = self.mimetype_params.get('charset') - if request_charset is not None: - return json.loads(self.data, encoding=request_charset) - return json.loads(self.data) + try: + if request_charset is not None: + return json.loads(self.data, encoding=request_charset) + return json.loads(self.data) + except ValueError, e: + return self.on_json_loading_failed(e) + + 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`. + + .. versionadded:: 0.8 + """ + raise BadRequest() def _load_form_data(self): RequestBase._load_form_data(self) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 5e592710..7c1f4b2a 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -984,6 +984,15 @@ class BasicFunctionalityTestCase(unittest.TestCase): class JSONTestCase(unittest.TestCase): + def test_json_bad_requests(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.assertEqual(rv.status_code, 400) + def test_json_body_encoding(self): app = flask.Flask(__name__) app.testing = True From 68473291344629dff3955fdc07776f2fca7b9b69 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 9 Aug 2011 14:51:06 +0200 Subject: [PATCH 0083/3143] Flask will now give you an error in debug mode if a post request caused a redirect by the routing system. --- flask/app.py | 20 ++++++++++++++++++-- flask/debughelpers.py | 26 ++++++++++++++++++++++++++ tests/flask_tests.py | 24 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 009841f8..00c36a38 100644 --- a/flask/app.py +++ b/flask/app.py @@ -18,7 +18,7 @@ from itertools import chain from functools import update_wrapper from werkzeug.datastructures import ImmutableDict -from werkzeug.routing import Map, Rule +from werkzeug.routing import Map, Rule, RequestRedirect from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed, BadRequest @@ -1134,6 +1134,22 @@ class Flask(_PackageBoundObject): return InternalServerError() return handler(e) + def raise_routing_exception(self, request): + """Exceptions that are recording during routing are reraised with + this method. During debug we are not reraising redirect requests + for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising + a different error instead to help debug situations. + + :internal: + """ + if not self.debug \ + or not isinstance(request.routing_exception, RequestRedirect) \ + or request.method in ('GET', 'HEAD', 'OPTIONS'): + raise request.routing_exception + + from .debughelpers import FormDataRoutingRedirect + raise FormDataRoutingRedirect(request) + def dispatch_request(self): """Does the request dispatching. Matches the URL and returns the return value of the view or error handler. This does not have to @@ -1146,7 +1162,7 @@ class Flask(_PackageBoundObject): """ req = _request_ctx_stack.top.request if req.routing_exception is not None: - raise req.routing_exception + self.raise_routing_exception(req) rule = req.url_rule # if we provide automatic options for this URL and the # request came with the OPTIONS method, reply automatically diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 7eae61c5..b4f73dd3 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -33,6 +33,32 @@ class DebugFilesKeyError(KeyError, AssertionError): return self.msg +class FormDataRoutingRedirect(AssertionError): + """This exception is raised by Flask in debug mode if it detects a + redirect caused by the routing system when the request method is not + GET, HEAD or OPTIONS. Reasoning: form data will be dropped. + """ + + def __init__(self, request): + exc = request.routing_exception + buf = ['A request was sent to this URL (%s) but a redirect was ' + 'issued automatically by the routing system to "%s".' + % (request.url, exc.new_url)] + + # In case just a slash was appended we can be extra helpful + if request.base_url + '/' == exc.new_url.split('?')[0]: + buf.append(' The URL was defined with a trailing slash so ' + 'Flask will automatically redirect to the URL ' + 'with the trailing slash if it was accessed ' + 'without one.') + + buf.append(' Make sure to directly send your %s-request to this URL ' + 'since we can\'t make browsers or HTTP clients redirect ' + 'with form data.' % request.method) + buf.append('\n\nNote: this exception is only raised in debug mode') + AssertionError.__init__(self, ''.join(buf).encode('utf-8')) + + def attach_enctype_error_multidict(request): """Since Flask 0.8 we're monkeypatching the files object in case a request is detected that does not use multipart form data but the files diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 7c1f4b2a..e5fbf20f 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -981,6 +981,30 @@ class BasicFunctionalityTestCase(unittest.TestCase): self.assertEqual(got, [42]) self.assert_(app.got_first_request) + def test_routing_redirect_debugging(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/foo/', methods=['GET', 'POST']) + def foo(): + return 'success' + with app.test_client() as c: + try: + c.post('/foo', data={}) + except AssertionError, 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)) + else: + self.fail('Expected exception') + + rv = c.get('/foo', data={}, follow_redirects=True) + self.assertEqual(rv.data, 'success') + + app.debug = False + with app.test_client() as c: + rv = c.post('/foo', data={}, follow_redirects=True) + self.assertEqual(rv.data, 'success') + class JSONTestCase(unittest.TestCase): From 92c7bdd2dd943b173476a6d2f26e0831579e2d3d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 9 Aug 2011 15:16:41 +0200 Subject: [PATCH 0084/3143] Documented Request.headers --- docs/api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index ff7e597a..e657bf6e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -59,6 +59,10 @@ Incoming Request Data of the time it is a better idea to use :attr:`data` which will give you that data as a string. The stream only returns the data once. + .. attribute:: headers + + The incoming request headers as a dictionary like object. + .. attribute:: data Contains the incoming request data as string in case it came with From 63876614b6734e6404f17c9d3d22b71b3f43d158 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 9 Aug 2011 16:16:04 +0200 Subject: [PATCH 0085/3143] Removed second mention of json. This fixes #290 --- docs/api.rst | 6 ------ flask/wrappers.py | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e657bf6e..6b695bfa 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -119,12 +119,6 @@ Incoming Request Data Libraries that do that are prototype, jQuery and Mochikit and probably some more. - .. attribute:: json - - Contains the parsed body of the JSON request if the mimetype of - the incoming data was `application/json`. This requires Python 2.6 - or an installed version of simplejson. - .. class:: request To access incoming request data, you can use the global `request` diff --git a/flask/wrappers.py b/flask/wrappers.py index 2d3d53f7..aed0a8d0 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -91,7 +91,9 @@ class Request(RequestBase): @cached_property def json(self): """If the mimetype is `application/json` this will contain the - parsed JSON data. + parsed JSON data. Otherwise this will be `None`. + + This requires Python 2.6 or an installed version of simplejson. """ if __debug__: _assert_have_json() From 153ecbc9202f249c4037602115ed2c2b5785550d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 13:34:58 +0200 Subject: [PATCH 0086/3143] Implemented instance paths --- CHANGES | 5 +++ flask/app.py | 79 +++++++++++++++++++++++++++++++++++++++----- tests/flask_tests.py | 18 ++++++++++ 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index c1d33869..817c21f4 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,11 @@ Relase date to be decided, codename to be chosen. - Malformed JSON data will now trigger a bad request HTTP exception instead 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 + 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. Version 0.7.3 ------------- diff --git a/flask/app.py b/flask/app.py index 00c36a38..4c969852 100644 --- a/flask/app.py +++ b/flask/app.py @@ -11,6 +11,7 @@ from __future__ import with_statement +import os import sys from threading import Lock from datetime import timedelta @@ -103,14 +104,33 @@ class Flask(_PackageBoundObject): pick up SQL queries in `yourapplication.app` and not `yourapplication.views.frontend`) - .. versionadded:: 0.5 - The `static_path` parameter was added. + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. :param import_name: the name of the application package - :param static_path: can be used to specify a different path for the - static files on the web. Defaults to ``/static``. - This does not affect the folder the files are served - *from*. + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: the folder with static files that should be served + at `static_url_path`. Defaults to the ``'static'`` + folder in the root path of the application. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to `True` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. """ #: The class that is used for request objects. See :class:`~flask.Request` @@ -238,7 +258,8 @@ class Flask(_PackageBoundObject): session_interface = SecureCookieSessionInterface() def __init__(self, import_name, static_path=None, static_url_path=None, - static_folder='static', template_folder='templates'): + static_folder='static', template_folder='templates', + instance_path=None, instance_relative_config=False): _PackageBoundObject.__init__(self, import_name, template_folder=template_folder) if static_path is not None: @@ -251,11 +272,21 @@ class Flask(_PackageBoundObject): self.static_url_path = static_url_path if static_folder is not None: self.static_folder = static_folder + if instance_path is None: + instance_path = self.auto_find_instance_path() + elif not os.path.isabs(instance_path): + raise ValueError('If an instance path is provided it must be ' + 'absolute. A relative path was given instead.') + + #: Holds the path to the instance folder. + #: + #: .. versionadded:: 0.8 + self.instance_path = instance_path #: The configuration dictionary as :class:`Config`. This behaves #: exactly like a regular dictionary but supports additional methods #: to load a config from files. - self.config = Config(self.root_path, self.default_config) + self.config = self.make_config(instance_relative_config) # Prepare the deferred setup of the logger. self._logger = None @@ -491,6 +522,38 @@ class Flask(_PackageBoundObject): """ return self._got_first_request + def make_config(self, instance_relative=False): + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + return Config(root_path, self.default_config) + + def auto_find_instance_path(self): + """Tries to locate the instance path if it was not provided to the + constructor of the application class. It will basically calculate + the path to a folder named ``instance`` next to your main file or + the package. + + .. versionadded:: 0.8 + """ + root_mod = sys.modules[self.import_name.split('.')[0]] + instance_path = None + if hasattr(root_mod, '__path__'): + package_dir = os.path.dirname(root_mod.__file__) + instance_path = os.path.join(package_dir, os.path.pardir) + else: + instance_path = os.path.dirname(root_mod.__file__) + basedir = os.path.normpath(os.path.abspath(instance_path)) + return os.path.join(basedir, 'instance') + def create_jinja_environment(self): """Creates the Jinja2 environment based on :attr:`jinja_options` and :meth:`select_jinja_autoescape`. Since 0.7 this also adds diff --git a/tests/flask_tests.py b/tests/flask_tests.py index e5fbf20f..6e9f4555 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1005,6 +1005,24 @@ class BasicFunctionalityTestCase(unittest.TestCase): rv = c.post('/foo', data={}, follow_redirects=True) self.assertEqual(rv.data, 'success') + def test_basic_instance_paths(self): + here = os.path.abspath(os.path.dirname(__file__)) + app = flask.Flask(__name__) + self.assertEqual(app.instance_path, os.path.join(here, 'instance')) + + app = flask.Flask(__name__, instance_path=here) + self.assertEqual(app.instance_path, here) + + try: + flask.Flask(__name__, instance_path='instance') + except ValueError, e: + self.assert_('must be absolute' in str(e)) + else: + self.fail('Expected value error') + + from blueprintapp import app + self.assertEqual(app.instance_path, os.path.join(here, 'instance')) + class JSONTestCase(unittest.TestCase): From 187cb80dcc1b087f1a7b7d4d67afbd531ad01cd2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 13:55:57 +0200 Subject: [PATCH 0087/3143] Documented instance root --- CHANGES | 3 ++- docs/config.rst | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ flask/app.py | 11 +++++++++ flask/helpers.py | 6 +++-- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 817c21f4..dc288966 100644 --- a/CHANGES +++ b/CHANGES @@ -30,7 +30,8 @@ Relase date to be decided, codename to be chosen. are located but also an instane 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. + the perfect place to put configuration files etc. For more information + see :ref:`instance-folders`. Version 0.7.3 ------------- diff --git a/docs/config.rst b/docs/config.rst index f50e3180..e1605723 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -267,3 +267,64 @@ your configuration files. However here a list of good recommendations: :ref:`fabric-deployment` pattern. .. _fabric: http://fabfile.org/ + + +.. _instance-folders: + +Instance Folders +---------------- + +.. versionadded:: 0.8 + +Flask 0.8 introduces instance folders. Flask for a long time made it +possible to refer to paths relative to the application's folder directly +(via :attr:`Flask.root_path`). This was also how many developers loaded +configurations stored next to the application. Unfortunately however this +only works well if applications are not packages in which case the root +path refers to the contents of the package. + +With Flask 0.8 a new attribute was introduced: +:attr:`Flask.instance_path`. It refers to a new concept called the +“instance folderâ€. The instance folder is designed to not be under +version control and be deployment specific. It's the perfect place to +drop things that either change at runtime or configuration files. + +To make it easier to put this folder into an ignore list for your version +control system it's called ``instance`` and placed directly next to your +package or module by default. This path can be overridden by specifying +the `instance_path` parameter to your application:: + + app = Flask(__name__, instance_path='/path/to/instance') + +Please keep in mind that this path *must* be absolute when provided. + +Since the config object provided loading of configuration files from +relative filenames we made it possible to change the loading via filenames +to be relative to the instance path if wanted. The behavior of relative +paths in config files can be flipped between “relative to the application +root†(the default) to “relative to instance folder†via the +`instance_relative_config` switch to the application constructor:: + + app = Flask(__name__, instance_relative_config=True) + +Here is a full example of how to configure Flask to preload the config +from a module and then override the config from a file in the config +folder if it exists:: + + app = Flask(__name__, instance_relative_config=True) + app.config.from_object('yourapplication.default_settings') + app.config.from_pyfile('application.cfg', silent=True) + +The path to the instance folder can be found via the +:attr:`Flask.instance_path`. Flask also provides a shortcut to open a +file from the instnace folder with :meth:`Flask.open_instance_resource`. + +Example usage for both:: + + filename = os.path.join(app.instance_root, 'application.cfg') + with open(filename) as f: + config = f.read() + + # or via open_instance_resource: + with app.open_instance_resource('application.cfg') as f: + config = f.read() diff --git a/flask/app.py b/flask/app.py index 4c969852..636da286 100644 --- a/flask/app.py +++ b/flask/app.py @@ -554,6 +554,17 @@ class Flask(_PackageBoundObject): basedir = os.path.normpath(os.path.abspath(instance_path)) return os.path.join(basedir, 'instance') + def open_instance_resource(self, resource, mode='rb'): + """Opens a resource from the application's instance folder + (:attr:`instance_path`). Otherwise works like + :meth:`open_resource`. Instance resources can also be opened for + writing. + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + """ + return open(os.path.join(self.instance_path, resource), mode) + def create_jinja_environment(self): """Creates the Jinja2 environment based on :attr:`jinja_options` and :meth:`select_jinja_autoescape`. Since 0.7 this also adds diff --git a/flask/helpers.py b/flask/helpers.py index dff79f68..338f5a5a 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -565,7 +565,7 @@ class _PackageBoundObject(object): raise RuntimeError('No static folder for this object') return send_from_directory(self.static_folder, filename) - def open_resource(self, resource): + def open_resource(self, resource, mode='rb'): """Opens a resource from the application's resource folder. To see how this works, consider the following folder structure:: @@ -587,4 +587,6 @@ class _PackageBoundObject(object): :param resource: the name of the resource. To access resources within subfolders use forward slashes as separator. """ - return open(os.path.join(self.root_path, resource), 'rb') + if mode not in ('r', 'rb'): + raise ValueError('Resources can only be opened for reading') + return open(os.path.join(self.root_path, resource), mode) From 9af75546f0b00c92574018498f5979322d10cc4c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 13:57:30 +0200 Subject: [PATCH 0088/3143] Mention default locations for instance folders --- docs/config.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index e1605723..94935a75 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -294,7 +294,18 @@ control system it's called ``instance`` and placed directly next to your package or module by default. This path can be overridden by specifying the `instance_path` parameter to your application:: - app = Flask(__name__, instance_path='/path/to/instance') + app = Flask(__name__, instance_path='/path/to/instance/folder') + +Default locations:: + + Module situation: + /myapp.py + /instance + + Package situation: + /myapp + /__init__.py + /instance Please keep in mind that this path *must* be absolute when provided. From 05204250553b41bb40f788d37d27091b61fc1d59 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 14:00:57 +0200 Subject: [PATCH 0089/3143] Fixed a typo --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 94935a75..57bea926 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -328,7 +328,7 @@ folder if it exists:: The path to the instance folder can be found via the :attr:`Flask.instance_path`. Flask also provides a shortcut to open a -file from the instnace folder with :meth:`Flask.open_instance_resource`. +file from the instance folder with :meth:`Flask.open_instance_resource`. Example usage for both:: From 175d43b2f9c7825b0df4c1049ab571cb1877a807 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 17:46:20 +0200 Subject: [PATCH 0090/3143] Instance paths are now moved into virtualenv/share/appname-instance if installed --- flask/app.py | 41 ++++++++++++++++++++++++++----- flask/helpers.py | 4 ++-- tests/flask_tests.py | 57 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/flask/app.py b/flask/app.py index 636da286..b8f07fcd 100644 --- a/flask/app.py +++ b/flask/app.py @@ -447,6 +447,23 @@ class Flask(_PackageBoundObject): error_handlers = property(_get_error_handlers, _set_error_handlers) del _get_error_handlers, _set_error_handlers + @locked_cached_property + def name(self): + """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 + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == '__main__': + fn = getattr(sys.modules['__main__'], '__file__', None) + if fn is None: + return 'unknown' + return os.path.splitext(os.path.basename(fn))[0] + return self.import_name + @property def propagate_exceptions(self): """Returns the value of the `PROPAGATE_EXCEPTIONS` configuration @@ -545,14 +562,26 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.8 """ root_mod = sys.modules[self.import_name.split('.')[0]] - instance_path = None + # we're not using root_mod.__file__ here since the module could be + # virtual. We're trusting the _PackageBoundObject to have calculated + # the proper name. + package_path = self.root_path if hasattr(root_mod, '__path__'): - package_dir = os.path.dirname(root_mod.__file__) - instance_path = os.path.join(package_dir, os.path.pardir) + package_path = os.path.dirname(package_path) + site_parent, site_folder = os.path.split(package_path) + + py_prefix = os.path.abspath(sys.prefix) + if package_path.startswith(py_prefix): + base_dir = py_prefix + elif site_folder == 'site-packages': + parent, folder = os.path.split(site_parent) + if folder.lower() == 'lib': + base_dir = parent + else: + base_dir = site_parent else: - instance_path = os.path.dirname(root_mod.__file__) - basedir = os.path.normpath(os.path.abspath(instance_path)) - return os.path.join(basedir, 'instance') + return os.path.join(package_path, 'instance') + return os.path.join(base_dir, 'share', self.name + '-instance') def open_instance_resource(self, resource, mode='rb'): """Opens a resource from the application's instance folder diff --git a/flask/helpers.py b/flask/helpers.py index 338f5a5a..89fc82cb 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -466,7 +466,7 @@ def send_from_directory(directory, filename, **options): return send_file(filename, conditional=True, **options) -def _get_package_path(name): +def get_package_path(name): """Returns the path to a package or cwd if that cannot be found.""" try: return os.path.abspath(os.path.dirname(sys.modules[name].__file__)) @@ -512,7 +512,7 @@ class _PackageBoundObject(object): self.template_folder = template_folder #: Where is the app root located? - self.root_path = _get_package_path(self.import_name) + self.root_path = get_package_path(self.import_name) self._static_folder = None self._static_url_path = None diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 6e9f4555..63ecad97 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1005,7 +1005,10 @@ class BasicFunctionalityTestCase(unittest.TestCase): rv = c.post('/foo', data={}, follow_redirects=True) self.assertEqual(rv.data, 'success') - def test_basic_instance_paths(self): + +class InstanceTestCase(unittest.TestCase): + + def test_uninstalled_module_paths(self): here = os.path.abspath(os.path.dirname(__file__)) app = flask.Flask(__name__) self.assertEqual(app.instance_path, os.path.join(here, 'instance')) @@ -1020,9 +1023,60 @@ class BasicFunctionalityTestCase(unittest.TestCase): else: self.fail('Expected value error') + def test_uninstalled_package_paths(self): from blueprintapp import app + here = os.path.abspath(os.path.dirname(__file__)) self.assertEqual(app.instance_path, os.path.join(here, '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', + 'site-packages', 'myapp.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assertEqual(mod.app.instance_path, + os.path.join(expected_prefix, 'share', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + def test_installed_package_paths(self): + import types + expected_prefix = os.path.abspath('foo') + package_path = os.path.join(expected_prefix, 'lib', + 'site-packages', 'myapp') + mod = types.ModuleType('myapp') + mod.__path__ = [package_path] + mod.__file__ = os.path.join(package_path, '__init__.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assertEqual(mod.app.instance_path, + os.path.join(expected_prefix, 'share', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + def test_prefix_installed_paths(self): + import types + expected_prefix = os.path.abspath(sys.prefix) + package_path = os.path.join(expected_prefix, 'lib', + 'site-packages', 'myapp') + mod = types.ModuleType('myapp') + mod.__path__ = [package_path] + mod.__file__ = os.path.join(package_path, '__init__.py') + sys.modules['myapp'] = mod + try: + mod.app = flask.Flask(mod.__name__) + self.assertEqual(mod.app.instance_path, + os.path.join(expected_prefix, 'share', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + class JSONTestCase(unittest.TestCase): @@ -2114,6 +2168,7 @@ def suite(): suite.addTest(unittest.makeSuite(SubdomainTestCase)) suite.addTest(unittest.makeSuite(ViewTestCase)) suite.addTest(unittest.makeSuite(DeprecationsTestCase)) + suite.addTest(unittest.makeSuite(InstanceTestCase)) if flask.json_available: suite.addTest(unittest.makeSuite(JSONTestCase)) if flask.signals_available: From e328eba97cc87a34e846693b62d17bdedaf27134 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 17:51:24 +0200 Subject: [PATCH 0091/3143] Corrected prefix detection --- flask/app.py | 2 ++ tests/flask_tests.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index b8f07fcd..03c7ce32 100644 --- a/flask/app.py +++ b/flask/app.py @@ -577,6 +577,8 @@ class Flask(_PackageBoundObject): parent, folder = os.path.split(site_parent) if folder.lower() == 'lib': base_dir = parent + elif os.path.basename(parent).lower() == 'lib': + base_dir = os.path.dirname(parent) else: base_dir = site_parent else: diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 63ecad97..7b3f334e 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1032,7 +1032,7 @@ class InstanceTestCase(unittest.TestCase): import types expected_prefix = os.path.abspath('foo') mod = types.ModuleType('myapp') - mod.__file__ = os.path.join(expected_prefix, 'lib', + mod.__file__ = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages', 'myapp.py') sys.modules['myapp'] = mod try: @@ -1046,7 +1046,7 @@ class InstanceTestCase(unittest.TestCase): def test_installed_package_paths(self): import types expected_prefix = os.path.abspath('foo') - package_path = os.path.join(expected_prefix, 'lib', + package_path = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages', 'myapp') mod = types.ModuleType('myapp') mod.__path__ = [package_path] @@ -1063,7 +1063,7 @@ class InstanceTestCase(unittest.TestCase): def test_prefix_installed_paths(self): import types expected_prefix = os.path.abspath(sys.prefix) - package_path = os.path.join(expected_prefix, 'lib', + package_path = os.path.join(expected_prefix, 'lib', 'python2.5', 'site-packages', 'myapp') mod = types.ModuleType('myapp') mod.__path__ = [package_path] From 74f4af59f631ef154ffe6f34d8e525a05f97d4a6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 17:51:49 +0200 Subject: [PATCH 0092/3143] Added comments for two branches in the instance detection --- flask/app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/app.py b/flask/app.py index 03c7ce32..2cad86e2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -575,8 +575,10 @@ class Flask(_PackageBoundObject): base_dir = py_prefix elif site_folder == 'site-packages': parent, folder = os.path.split(site_parent) + # Windows like installations if folder.lower() == 'lib': base_dir = parent + # UNIX like installations elif os.path.basename(parent).lower() == 'lib': base_dir = os.path.dirname(parent) else: From fb1a6730cfbf63b6d6ccda93381f0823d8b6c85a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 18:00:16 +0200 Subject: [PATCH 0093/3143] Leave eggs when finding the instance path --- flask/app.py | 6 +++++- tests/flask_tests.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 2cad86e2..14b6d138 100644 --- a/flask/app.py +++ b/flask/app.py @@ -568,8 +568,12 @@ class Flask(_PackageBoundObject): package_path = self.root_path if hasattr(root_mod, '__path__'): package_path = os.path.dirname(package_path) - site_parent, site_folder = os.path.split(package_path) + # leave the egg wrapper folder or the actual .egg on the filesystem + if os.path.basename(package_path).endswith('.egg'): + package_path = os.path.dirname(package_path) + + site_parent, site_folder = os.path.split(package_path) py_prefix = os.path.abspath(sys.prefix) if package_path.startswith(py_prefix): base_dir = py_prefix diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 7b3f334e..4b0150af 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1077,6 +1077,23 @@ class InstanceTestCase(unittest.TestCase): finally: sys.modules['myapp'] = None + 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 + try: + mod.app = flask.Flask(mod.__name__) + self.assertEqual(mod.app.instance_path, + os.path.join(expected_prefix, 'share', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + class JSONTestCase(unittest.TestCase): From b3aaf6d5ca4fa53cc78456b2df01ab4d11cec84a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 23:19:33 +0200 Subject: [PATCH 0094/3143] Refactored package finding --- flask/app.py | 34 +++++----------------------------- flask/helpers.py | 37 +++++++++++++++++++++++++++++++++++++ tests/flask_tests.py | 8 ++++---- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/flask/app.py b/flask/app.py index 14b6d138..52a54b29 100644 --- a/flask/app.py +++ b/flask/app.py @@ -24,7 +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 + locked_cached_property, _tojson_filter, _endpoint_from_view_func, \ + find_package from .wrappers import Request, Response from .config import ConfigAttribute, Config from .ctx import RequestContext @@ -561,35 +562,10 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.8 """ - root_mod = sys.modules[self.import_name.split('.')[0]] - # we're not using root_mod.__file__ here since the module could be - # virtual. We're trusting the _PackageBoundObject to have calculated - # the proper name. - package_path = self.root_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 - if os.path.basename(package_path).endswith('.egg'): - package_path = os.path.dirname(package_path) - - site_parent, site_folder = os.path.split(package_path) - py_prefix = os.path.abspath(sys.prefix) - if package_path.startswith(py_prefix): - base_dir = py_prefix - elif site_folder == 'site-packages': - parent, folder = os.path.split(site_parent) - # Windows like installations - if folder.lower() == 'lib': - base_dir = parent - # UNIX like installations - elif os.path.basename(parent).lower() == 'lib': - base_dir = os.path.dirname(parent) - else: - base_dir = site_parent - else: + prefix, package_path = find_package(self.import_name) + if prefix is None: return os.path.join(package_path, 'instance') - return os.path.join(base_dir, 'share', self.name + '-instance') + return os.path.join(prefix, 'var', self.name + '-instance') def open_instance_resource(self, resource, mode='rb'): """Opens a resource from the application's instance folder diff --git a/flask/helpers.py b/flask/helpers.py index 89fc82cb..d3ce50b8 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -474,6 +474,43 @@ def get_package_path(name): return os.getcwd() +def find_package(import_name): + """Finds a package and returns the prefix (or None if the package is + not installed) as well as the folder that contains the package or + module as a tuple. + """ + root_mod = sys.modules[import_name.split('.')[0]] + package_path = getattr(root_mod, '__file__', None) + if package_path is None: + 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 + 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) + py_prefix = os.path.abspath(sys.prefix) + if test_package_path.startswith(py_prefix): + return py_prefix, package_path + elif site_folder.lower() == 'site-packages': + parent, folder = os.path.split(site_parent) + # Windows like installations + if folder.lower() == 'lib': + base_dir = parent + # UNIX like installations + elif os.path.basename(parent).lower() == 'lib': + base_dir = os.path.dirname(parent) + else: + base_dir = site_parent + return base_dir, package_path + return None, package_path + + class locked_cached_property(object): """A decorator that converts a function into a lazy property. The function wrapped is called the first time to retrieve the result diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 4b0150af..4404f931 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1038,7 +1038,7 @@ class InstanceTestCase(unittest.TestCase): try: mod.app = flask.Flask(mod.__name__) self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'share', + os.path.join(expected_prefix, 'var', 'myapp-instance')) finally: sys.modules['myapp'] = None @@ -1055,7 +1055,7 @@ class InstanceTestCase(unittest.TestCase): try: mod.app = flask.Flask(mod.__name__) self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'share', + os.path.join(expected_prefix, 'var', 'myapp-instance')) finally: sys.modules['myapp'] = None @@ -1072,7 +1072,7 @@ class InstanceTestCase(unittest.TestCase): try: mod.app = flask.Flask(mod.__name__) self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'share', + os.path.join(expected_prefix, 'var', 'myapp-instance')) finally: sys.modules['myapp'] = None @@ -1089,7 +1089,7 @@ class InstanceTestCase(unittest.TestCase): try: mod.app = flask.Flask(mod.__name__) self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'share', + os.path.join(expected_prefix, 'var', 'myapp-instance')) finally: sys.modules['myapp'] = None From c3844d11026f84400d5a088dd5c24c6a4a04dcd1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 23:21:43 +0200 Subject: [PATCH 0095/3143] Rename _get_package_path to get_root_path to avoid confusion --- flask/helpers.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index d3ce50b8..04d23a07 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -466,8 +466,12 @@ def send_from_directory(directory, filename, **options): return send_file(filename, conditional=True, **options) -def get_package_path(name): - """Returns the path to a package or cwd if that cannot be found.""" +def get_root_path(name): + """Returns the path to a package or cwd if that cannot be found. This + returns the path of a package or the folder that contains a module. + + Not to be confused with the package path returned by :func:`find_package`. + """ try: return os.path.abspath(os.path.dirname(sys.modules[name].__file__)) except (KeyError, AttributeError): @@ -549,7 +553,7 @@ class _PackageBoundObject(object): self.template_folder = template_folder #: Where is the app root located? - self.root_path = get_package_path(self.import_name) + self.root_path = get_root_path(self.import_name) self._static_folder = None self._static_url_path = None From 1d6f86bc87253c1dc1cf0c156f421214c38c3202 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 23:25:33 +0200 Subject: [PATCH 0096/3143] Updated docstring --- flask/helpers.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 04d23a07..a260b03f 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -466,23 +466,29 @@ def send_from_directory(directory, filename, **options): return send_file(filename, conditional=True, **options) -def get_root_path(name): +def get_root_path(import_name): """Returns the path to a package or cwd if that cannot be found. This returns the path of a package or the folder that contains a module. Not to be confused with the package path returned by :func:`find_package`. """ + __import__(import_name) try: - return os.path.abspath(os.path.dirname(sys.modules[name].__file__)) - except (KeyError, AttributeError): + directory = os.path.dirname(sys.modules[import_name].__file__) + return os.path.abspath(directory) + except AttributeError: return os.getcwd() def find_package(import_name): """Finds a package and returns the prefix (or None if the package is not installed) as well as the folder that contains the package or - module as a tuple. + module as a tuple. The package path returned is the module that would + have to be added to the pythonpath in order to make it possible to + 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: From eb9a14e1581fd379f25986bb206fe0d8fdc6b1a3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 23:40:53 +0200 Subject: [PATCH 0097/3143] Split up a test into two --- tests/flask_tests.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 4404f931..220bf4cb 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1008,14 +1008,8 @@ class BasicFunctionalityTestCase(unittest.TestCase): class InstanceTestCase(unittest.TestCase): - def test_uninstalled_module_paths(self): + def test_explicit_instance_paths(self): here = os.path.abspath(os.path.dirname(__file__)) - app = flask.Flask(__name__) - self.assertEqual(app.instance_path, os.path.join(here, 'instance')) - - app = flask.Flask(__name__, instance_path=here) - self.assertEqual(app.instance_path, here) - try: flask.Flask(__name__, instance_path='instance') except ValueError, e: @@ -1023,6 +1017,14 @@ class InstanceTestCase(unittest.TestCase): else: self.fail('Expected value error') + app = flask.Flask(__name__, instance_path=here) + self.assertEqual(app.instance_path, here) + + def test_uninstalled_module_paths(self): + here = os.path.abspath(os.path.dirname(__file__)) + app = flask.Flask(__name__) + self.assertEqual(app.instance_path, os.path.join(here, 'instance')) + def test_uninstalled_package_paths(self): from blueprintapp import app here = os.path.abspath(os.path.dirname(__file__)) From 45d963d6daa4315388530d58c777b838d5465c65 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 10 Aug 2011 23:50:43 +0200 Subject: [PATCH 0098/3143] The session interface is new in 0.8 not 0.7 --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 52a54b29..35577984 100644 --- a/flask/app.py +++ b/flask/app.py @@ -255,7 +255,7 @@ class Flask(_PackageBoundObject): #: the session interface to use. By default an instance of #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. #: - #: .. versionadded:: 0.7 + #: .. versionadded:: 0.8 session_interface = SecureCookieSessionInterface() def __init__(self, import_name, static_path=None, static_url_path=None, From 77492f8afad5b8a3cc8bca297bf40c73b9e18ff4 Mon Sep 17 00:00:00 2001 From: Alexis Metaireau Date: Sat, 13 Aug 2011 01:22:10 +0300 Subject: [PATCH 0099/3143] typo --- docs/signals.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/signals.rst b/docs/signals.rst index a5821603..0d1d9eea 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -124,7 +124,7 @@ debugging. You can access the name of the signal with the .. admonition:: For Extension Developers - If you are writing a Flask extension and you to gracefully degrade for + If you are writing a Flask extension and you want to gracefully degrade for missing blinker installations, you can do so by using the :class:`flask.signals.Namespace` class. From 713ced603e246b34fd88a417ae0be4d71505be70 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Wed, 17 Aug 2011 08:38:34 +0300 Subject: [PATCH 0100/3143] Improve configuration docs a bit --- docs/config.rst | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 57bea926..4f37b9b9 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -5,9 +5,10 @@ Configuration Handling .. versionadded:: 0.3 -Applications need some kind of configuration. There are different things -you might want to change like toggling debug mode, the secret key, and a -lot of very similar things. +Applications need some kind of configuration. There are different settings +you might want to change depending on the application environment like +toggling the debug mode, setting the secret key, and other such +environment-specific things. The way Flask is designed usually requires the configuration to be available when the application starts up. You can hardcode the @@ -31,8 +32,7 @@ can be modified just like any dictionary:: app.config['DEBUG'] = True Certain configuration values are also forwarded to the -:attr:`~flask.Flask` object so that you can read and write them from -there:: +:attr:`~flask.Flask` object so you can read and write them from there:: app.debug = True @@ -139,10 +139,11 @@ The following configuration values are used internally by Flask: Configuring from Files ---------------------- -Configuration becomes more useful if you can configure from a file, and -ideally that file would be outside of the actual application package so that -you can install the package with distribute (:ref:`distribute-deployment`) -and still modify that file afterwards. +Configuration becomes more useful if you can store it in a separate file, +ideally located outside the actual application package. This makes +packaging and distributing your application possible via various package +handling tools (:ref:`distribute-deployment`) and finally modifying the +configuration file afterwards. So a common pattern is this:: @@ -170,12 +171,13 @@ The configuration files themselves are actual Python files. Only values in uppercase are actually stored in the config object later on. So make sure to use uppercase letters for your config keys. -Here is an example configuration file:: +Here is an example of a configuration file:: + # Example configuration DEBUG = False SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83' -Make sure to load the configuration very early on so that extensions have +Make sure to load the configuration very early on, so that extensions have the ability to access the configuration when starting up. There are other methods on the config object as well to load from individual files. For a complete reference, read the :class:`~flask.Config` object's @@ -186,9 +188,9 @@ Configuration Best Practices ---------------------------- The downside with the approach mentioned earlier is that it makes testing -a little harder. There is no one 100% solution for this problem in -general, but there are a couple of things you can do to improve that -experience: +a little harder. There is no single 100% solution for this problem in +general, but there are a couple of things you can keep in mind to improve +that experience: 1. create your application in a function and register blueprints on it. That way you can create multiple instances of your application with @@ -203,10 +205,10 @@ experience: Development / Production ------------------------ -Most applications need more than one configuration. There will at least -be a separate configuration for a production server and one used during -development. The easiest way to handle this is to use a default -configuration that is always loaded and part of version control, and a +Most applications need more than one configuration. There should be at +least separate configurations for the production server and the one used +during development. The easiest way to handle this is to use a default +configuration that is always loaded and part of the version control, and a separate configuration that overrides the values as necessary as mentioned in the example above:: From 68ec5a30687a89a56406d8abd603b51a6645856e Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 25 Aug 2011 11:46:05 +0100 Subject: [PATCH 0101/3143] Allow passing the endpoint to the route decorators on Flask's `application` and `blueprints`. --- flask/app.py | 3 ++- flask/blueprints.py | 3 ++- tests/flask_tests.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 35577984..0597ba5b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -942,7 +942,8 @@ class Flask(_PackageBoundObject): :class:`~werkzeug.routing.Rule` object. """ def decorator(f): - self.add_url_rule(rule, None, f, **options) + endpoint = options.pop("endpoint", None) + self.add_url_rule(rule, endpoint, f, **options) return f return decorator diff --git a/flask/blueprints.py b/flask/blueprints.py index 075961ab..6d693781 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -157,7 +157,8 @@ class Blueprint(_PackageBoundObject): :func:`url_for` function is prefixed with the name of the blueprint. """ def decorator(f): - self.add_url_rule(rule, f.__name__, f, **options) + endpoint = options.pop("endpoint", f.__name__) + self.add_url_rule(rule, endpoint, f, **options) return f return decorator diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 220bf4cb..3753218e 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1005,6 +1005,32 @@ class BasicFunctionalityTestCase(unittest.TestCase): rv = c.post('/foo', data={}, follow_redirects=True) self.assertEqual(rv.data, 'success') + def test_route_decorator_custom_endpoint(self): + app = flask.Flask(__name__) + app.debug = True + + @app.route('/foo/') + def foo(): + return flask.request.endpoint + + @app.route('/bar/', endpoint='bar') + def for_bar(): + return flask.request.endpoint + + @app.route('/bar/123', endpoint='123') + def for_bar_foo(): + return flask.request.endpoint + + with app.test_request_context(): + assert flask.url_for('foo') == '/foo/' + assert flask.url_for('bar') == '/bar/' + 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') + class InstanceTestCase(unittest.TestCase): @@ -1687,6 +1713,40 @@ class BlueprintTestCase(unittest.TestCase): self.assertEqual(c.get('/').data, '1') self.assertEqual(c.get('/page/2').data, '2') + def test_route_decorator_custom_endpoint(self): + + bp = flask.Blueprint('bp', __name__) + + @bp.route('/foo') + def foo(): + return flask.request.endpoint + + @bp.route('/bar', endpoint='bar') + def foo_bar(): + return flask.request.endpoint + + @bp.route('/bar/123', endpoint='123') + def foo_bar_foo(): + return flask.request.endpoint + + @bp.route('/bar/foo') + def bar_foo(): + return flask.request.endpoint + + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + + @app.route('/') + def index(): + return flask.request.endpoint + + c = app.test_client() + 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') + class SendfileTestCase(unittest.TestCase): From 04e87a93def1593dd886931e0f9e1d4f37f8d243 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 25 Aug 2011 12:01:41 +0100 Subject: [PATCH 0102/3143] Add endpoint parameter documentation to the route decorator. --- flask/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/app.py b/flask/app.py index 0597ba5b..67314fc8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -938,6 +938,9 @@ class Flask(_PackageBoundObject): subdomain matching is in use. :param strict_slashes: can be used to disable the strict slashes setting for this rule. See above. + :param endpoint: Since version 0.8 you can also pass the enpoint, + it will be used instead of generating the endpoint + from the function name. :param options: other options to be forwarded to the underlying :class:`~werkzeug.routing.Rule` object. """ From c844d02f1c7558e959891de75494cffd22dc323f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 12:13:55 +0100 Subject: [PATCH 0103/3143] Added the APPLICATION_ROOT configuration variable which is used by session backends. --- CHANGES | 1 + docs/config.rst | 10 +++++++++- flask/app.py | 1 + flask/sessions.py | 12 ++++++++++-- tests/flask_tests.py | 22 ++++++++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index dc288966..6ba980aa 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Relase date to be decided, codename to be chosen. conceptionally only instance depending and outside version control so it's the perfect place to put configuration files etc. For more information see :ref:`instance-folders`. +- Added the ``APPLICATION_ROOT`` configuration variable. Version 0.7.3 ------------- diff --git a/docs/config.rst b/docs/config.rst index 57bea926..eb9d0e06 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -77,6 +77,13 @@ The following configuration values are used internally by Flask: ``SERVER_NAME`` the name and port number of the server. Required for subdomain support (e.g.: ``'localhost:5000'``) +``APPLICATION_ROOT`` If the application does not occupy + a whole domain or subdomain this can + be set to the path where the application + is configured to live. This is for + session cookie as path value. If + domains are used, this should be + ``None``. ``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will reject incoming requests with a content length greater than this by @@ -134,7 +141,8 @@ The following configuration values are used internally by Flask: ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` .. versionadded:: 0.8 - ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS`` + ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, + ``APPLICATION_ROOT`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 35577984..6ad69975 100644 --- a/flask/app.py +++ b/flask/app.py @@ -236,6 +236,7 @@ class Flask(_PackageBoundObject): 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'SERVER_NAME': None, + 'APPLICATION_ROOT': None, 'MAX_CONTENT_LENGTH': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False diff --git a/flask/sessions.py b/flask/sessions.py index ee006cda..fda84a25 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -127,6 +127,13 @@ class SessionInterface(object): # chop of the port which is usually not supported by browsers return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] + def get_cookie_path(self, app): + """Returns the path for which the cookie should be valid. The + default implementation uses the value from the ``APPLICATION_ROOT`` + configuration variable or uses ``/`` if it's `None`. + """ + return app.config['APPLICATION_ROOT'] or '/' + def get_expiration_time(self, app, session): """A helper method that returns an expiration date for the session or `None` if the session is linked to the browser session. The @@ -169,9 +176,10 @@ class SecureCookieSessionInterface(SessionInterface): 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) if session.modified and not session: - response.delete_cookie(app.session_cookie_name, + response.delete_cookie(app.session_cookie_name, path=path, domain=domain) else: - session.save_cookie(response, app.session_cookie_name, + session.save_cookie(response, app.session_cookie_name, path=path, expires=expires, httponly=True, domain=domain) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 220bf4cb..db8275d6 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -339,6 +339,28 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert 'domain=.example.com' in rv.headers['set-cookie'].lower() assert 'httponly' in rv.headers['set-cookie'].lower() + def test_session_using_application_root(self): + class PrefixPathMiddleware(object): + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + def __call__(self, environ, start_response): + environ['SCRIPT_NAME'] = self.prefix + return self.app(environ, start_response) + + app = flask.Flask(__name__) + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') + app.config.update( + SECRET_KEY='foo', + APPLICATION_ROOT='/bar' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/') + assert 'path=/bar' in rv.headers['set-cookie'].lower() + def test_missing_session(self): app = flask.Flask(__name__) def expect_exception(f, *args, **kwargs): From a5da2c98f3ed2f9fe254e3b61799b06fa230a175 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:18:39 +0100 Subject: [PATCH 0104/3143] Implemented flask.testing.TestClient.session_transaction for quick session modifications in test environments. --- CHANGES | 2 ++ docs/api.rst | 9 +++++++ flask/app.py | 2 ++ flask/testing.py | 58 +++++++++++++++++++++++++++++++++++++++++--- tests/flask_tests.py | 45 ++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 6ba980aa..a451b978 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,8 @@ Relase date to be decided, codename to be chosen. the perfect place to put configuration files etc. For more information see :ref:`instance-folders`. - Added the ``APPLICATION_ROOT`` configuration variable. +- Implemented :meth:`~flask.testing.TestClient.session_transaction` to + easily modify sessions from the test environment. Version 0.7.3 ------------- diff --git a/docs/api.rst b/docs/api.rst index 6b695bfa..f4fab86f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -218,6 +218,15 @@ implementation that Flask is using. :members: +Test Client +----------- + +.. currentmodule:: flask.testing + +.. autoclass:: TestClient + :members: + + Application Globals ------------------- diff --git a/flask/app.py b/flask/app.py index 6ad69975..20cbca52 100644 --- a/flask/app.py +++ b/flask/app.py @@ -706,6 +706,8 @@ class Flask(_PackageBoundObject): rv = c.get('/?vodka=42') assert request.args['vodka'] == '42' + See :class:`~flask.testing.TestClient` for more information. + .. versionchanged:: 0.4 added support for `with` block usage for the client. diff --git a/flask/testing.py b/flask/testing.py index 06a2c016..c1844c00 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,19 +10,69 @@ :license: BSD, see LICENSE for more details. """ +from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack class FlaskClient(Client): - """Works like a regular Werkzeug test client but has some - knowledge about how Flask works to defer the cleanup of the - request context stack to the end of a with body when used - in a with statement. + """Works like a regular Werkzeug test client but has some knowledge about + how Flask works to defer the cleanup of the request context stack to the + end of a with body when used in a with statement. For general information + about how to use this class refer to :class:`werkzeug.test.Client`. + + Basic usage is outlined in the :ref:`testing` chapter. """ preserve_context = context_preserved = False + @contextmanager + def session_transaction(self, *args, **kwargs): + """When used in combination with a with statement this opens a + session transaction. This can be used to modify the session that + the test client uses. Once the with block is left the session is + stored back. + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + app = self.application + environ_overrides = kwargs.pop('environ_overrides', {}) + if self.cookie_jar is not None: + self.cookie_jar.inject_wsgi(environ_overrides) + outer_reqctx = _request_ctx_stack.top + with app.test_request_context(*args, **kwargs) as c: + sess = app.open_session(c.request) + if sess is None: + raise RuntimeError('Session backend did not open a session. ' + 'Check the configuration') + + # Since we have to open a new request context for the session + # handling we want to make sure that we hide out own context + # from the caller. By pushing the original request context + # (or None) on top of this and popping it we get exactly that + # behavior. It's important to not use the push and pop + # methods of the actual request context object since that would + # mean that cleanup handlers are called + _request_ctx_stack.push(outer_reqctx) + try: + yield sess + finally: + _request_ctx_stack.pop() + + resp = app.response_class() + if not app.session_interface.is_null_session(sess): + app.save_session(sess, resp) + if self.cookie_jar is not None: + headers = resp.get_wsgi_headers(c.request.environ) + self.cookie_jar.extract_wsgi(c.request.environ, headers) + def open(self, *args, **kwargs): if self.context_preserved: _request_ctx_stack.pop() diff --git a/tests/flask_tests.py b/tests/flask_tests.py index db8275d6..3c125be4 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1028,6 +1028,50 @@ class BasicFunctionalityTestCase(unittest.TestCase): self.assertEqual(rv.data, 'success') +class TestToolsTestCase(unittest.TestCase): + + def test_session_transactions(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + @app.route('/') + def index(): + return unicode(flask.session['foo']) + + with app.test_client() as c: + with c.session_transaction() as sess: + self.assertEqual(len(sess), 0) + sess['foo'] = [42] + self.assertEqual(len(sess), 1) + rv = c.get('/') + self.assertEqual(rv.data, '[42]') + + def test_session_transactions_no_null_sessions(self): + app = flask.Flask(__name__) + app.testing = True + + with app.test_client() as c: + try: + with c.session_transaction() as sess: + pass + except RuntimeError, e: + self.assert_('Session backend did not open a session' in str(e)) + else: + self.fail('Expected runtime error') + + def test_session_transactions_keep_context(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + with app.test_client() as c: + rv = c.get('/') + req = flask.request._get_current_object() + with c.session_transaction(): + self.assert_(req is flask.request._get_current_object()) + + class InstanceTestCase(unittest.TestCase): def test_explicit_instance_paths(self): @@ -2209,6 +2253,7 @@ def suite(): suite.addTest(unittest.makeSuite(SubdomainTestCase)) suite.addTest(unittest.makeSuite(ViewTestCase)) suite.addTest(unittest.makeSuite(DeprecationsTestCase)) + suite.addTest(unittest.makeSuite(TestToolsTestCase)) suite.addTest(unittest.makeSuite(InstanceTestCase)) if flask.json_available: suite.addTest(unittest.makeSuite(JSONTestCase)) From 001a5128d87e2cb934d14244d901d47e01980873 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:20:40 +0100 Subject: [PATCH 0105/3143] Refactored tests to use a different subclass --- tests/flask_tests.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 3c125be4..743339f0 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -95,7 +95,11 @@ def emits_module_deprecation_warning(f): return update_wrapper(new_f, f) -class ContextTestCase(unittest.TestCase): +class FlaskTestCase(unittest.TestCase): + pass + + +class ContextTestCase(FlaskTestCase): def test_context_binding(self): app = flask.Flask(__name__) @@ -172,7 +176,7 @@ class ContextTestCase(unittest.TestCase): raise AssertionError('some kind of exception expected') -class BasicFunctionalityTestCase(unittest.TestCase): +class BasicFunctionalityTestCase(FlaskTestCase): def test_options_work(self): app = flask.Flask(__name__) @@ -1028,7 +1032,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): self.assertEqual(rv.data, 'success') -class TestToolsTestCase(unittest.TestCase): +class TestToolsTestCase(FlaskTestCase): def test_session_transactions(self): app = flask.Flask(__name__) @@ -1072,7 +1076,7 @@ class TestToolsTestCase(unittest.TestCase): self.assert_(req is flask.request._get_current_object()) -class InstanceTestCase(unittest.TestCase): +class InstanceTestCase(FlaskTestCase): def test_explicit_instance_paths(self): here = os.path.abspath(os.path.dirname(__file__)) @@ -1163,7 +1167,7 @@ class InstanceTestCase(unittest.TestCase): sys.modules['myapp'] = None -class JSONTestCase(unittest.TestCase): +class JSONTestCase(FlaskTestCase): def test_json_bad_requests(self): app = flask.Flask(__name__) @@ -1239,7 +1243,7 @@ class JSONTestCase(unittest.TestCase): test_modified_url_encoding = None -class TemplatingTestCase(unittest.TestCase): +class TemplatingTestCase(FlaskTestCase): def test_context_processing(self): app = flask.Flask(__name__) @@ -1361,7 +1365,7 @@ class TemplatingTestCase(unittest.TestCase): assert rv.data == 'Hello Custom World!' -class ModuleTestCase(unittest.TestCase): +class ModuleTestCase(FlaskTestCase): @emits_module_deprecation_warning def test_basic_module(self): @@ -1587,7 +1591,7 @@ class ModuleTestCase(unittest.TestCase): assert c.get('/foo/bar').data == 'bar' -class BlueprintTestCase(unittest.TestCase): +class BlueprintTestCase(FlaskTestCase): def test_blueprint_specific_error_handling(self): frontend = flask.Blueprint('frontend', __name__) @@ -1754,7 +1758,7 @@ class BlueprintTestCase(unittest.TestCase): self.assertEqual(c.get('/page/2').data, '2') -class SendfileTestCase(unittest.TestCase): +class SendfileTestCase(FlaskTestCase): def test_send_file_regular(self): app = flask.Flask(__name__) @@ -1854,7 +1858,7 @@ class SendfileTestCase(unittest.TestCase): assert options['filename'] == 'index.txt' -class LoggingTestCase(unittest.TestCase): +class LoggingTestCase(FlaskTestCase): def test_logger_cache(self): app = flask.Flask(__name__) @@ -1938,7 +1942,7 @@ class LoggingTestCase(unittest.TestCase): assert rv.data == 'Hello Server Error' -class ConfigTestCase(unittest.TestCase): +class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): assert app.secret_key == 'devkey' @@ -1998,7 +2002,7 @@ class ConfigTestCase(unittest.TestCase): assert not app.config.from_pyfile('missing.cfg', silent=True) -class SubdomainTestCase(unittest.TestCase): +class SubdomainTestCase(FlaskTestCase): def test_basic_support(self): app = flask.Flask(__name__) @@ -2061,7 +2065,7 @@ class SubdomainTestCase(unittest.TestCase): assert rv.data == 'Outside' -class TestSignals(unittest.TestCase): +class TestSignals(FlaskTestCase): def test_template_rendered(self): app = flask.Flask(__name__) @@ -2144,7 +2148,7 @@ class TestSignals(unittest.TestCase): flask.got_request_exception.disconnect(record, app) -class ViewTestCase(unittest.TestCase): +class ViewTestCase(FlaskTestCase): def common_test(self, app): c = app.test_client() @@ -2219,7 +2223,7 @@ class ViewTestCase(unittest.TestCase): self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) -class DeprecationsTestCase(unittest.TestCase): +class DeprecationsTestCase(FlaskTestCase): def test_init_jinja_globals(self): class MyFlask(flask.Flask): From f051939d8ba1c5ec585fd4db1f83237a66020c0a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:24:10 +0100 Subject: [PATCH 0106/3143] Test that we're not leaking a request context in the testsuite, fixed a leak --- tests/flask_tests.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 743339f0..62814882 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -96,7 +96,15 @@ def emits_module_deprecation_warning(f): class FlaskTestCase(unittest.TestCase): - pass + + 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.assertEqual(flask._request_ctx_stack.top, None) + + def tearDown(self): + unittest.TestCase.tearDown(self) + self.ensure_clean_request_context() class ContextTestCase(FlaskTestCase): @@ -1881,23 +1889,23 @@ class LoggingTestCase(FlaskTestCase): @app.route('/exc') def exc(): 1/0 - c = app.test_client() - with catch_stderr() as err: - c.get('/') - out = err.getvalue() - assert 'WARNING in flask_tests [' in out - assert 'flask_tests.py' in out - assert 'the standard library is dead' in out - assert 'this is a debug statement' in out + with app.test_client() as c: + with catch_stderr() as err: + c.get('/') + out = err.getvalue() + assert 'WARNING in flask_tests [' in out + assert 'flask_tests.py' in out + assert 'the standard library is dead' in out + assert 'this is a debug statement' in out - with catch_stderr() as err: - try: - c.get('/exc') - except ZeroDivisionError: - pass - else: - assert False, 'debug log ate the exception' + with catch_stderr() as err: + try: + c.get('/exc') + except ZeroDivisionError: + pass + else: + assert False, 'debug log ate the exception' def test_exception_logging(self): out = StringIO() From 311ac0f533bf5a308200a51b861a9728b9223276 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 15:33:03 +0100 Subject: [PATCH 0107/3143] Ensure that nobody can nest test client invocations --- flask/testing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask/testing.py b/flask/testing.py index c1844c00..dfdaff57 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -108,6 +108,8 @@ class FlaskClient(Client): self.context_preserved = _request_ctx_stack.top is not old def __enter__(self): + if self.preserve_context: + raise RuntimeError('Cannot nest client invocations') self.preserve_context = True return self From 1ea3d4ea5300d9f456622da0cc55394250fa9b4c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 16:13:43 +0100 Subject: [PATCH 0108/3143] Updated documentation regarding the session transactions --- docs/testing.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/testing.rst b/docs/testing.rst index ed5765ea..ed44e5a2 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -273,3 +273,35 @@ is no longer available (because you are trying to use it outside of the actual r 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 +-------------------------------- + +.. versionadded:: 0.8 + +Sometimes it can be very helpful to access or modify the sessions from the +test client. Generally there are two ways for this. Ify ou just want to +ensure that a session has certain keys set to certain values you can just +keep the context around and access :data:`flask.session`:: + + with app.test_client() as c: + rv = c.get('/') + assert flask.session['foo'] == 42 + +This however does not make it possible to also modify the session or to +access the session before a request was fired. Starting with Flask 0.8 we +provide a so called “session transaction†which simulates the appropriate +calls to open a session in the context of the test client and to modify +it. At the end of the transaction the session is stored. This works +independently of the session backend used:: + + with app.test_client() as c: + with c.session_transaction() as sess: + sess['a_key'] = 'a value' + + # once this is reached the session was stored + +Note that in this case you have to use the ``sess`` object instead of the +:data:`flask.session` proxy. The object however itself will provide the +same interface. From 03a71e022919a0e1bff6606400e9d5ae234b8244 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 25 Aug 2011 17:26:57 +0100 Subject: [PATCH 0109/3143] Deny dot's in blueprint endpoints. Add tests for that too. --- flask/blueprints.py | 2 ++ tests/flask_tests.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/flask/blueprints.py b/flask/blueprints.py index 6d693781..8bfc610e 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -166,6 +166,8 @@ class Blueprint(_PackageBoundObject): """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for the :func:`url_for` function is prefixed with the name of the blueprint. """ + if endpoint: + assert '.' not in endpoint, "Blueprint endpoint's should not contain dot's" self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options)) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 3753218e..d5f7413d 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1747,6 +1747,57 @@ class BlueprintTestCase(unittest.TestCase): self.assertEqual(c.get('/py/bar/123').data, 'bp.123') self.assertEqual(c.get('/py/bar/foo').data, 'bp.bar_foo') + def test_route_decorator_custom_endpoint_with_dots(self): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/foo') + def foo(): + return flask.request.endpoint + + try: + @bp.route('/bar', endpoint='bar.bar') + def foo_bar(): + return flask.request.endpoint + except AssertionError: + pass + else: + raise AssertionError('expected AssertionError not raised') + + try: + @bp.route('/bar/123', endpoint='bar.123') + def foo_bar_foo(): + return flask.request.endpoint + except AssertionError: + pass + else: + raise AssertionError('expected AssertionError not raised') + + def foo_foo_foo(): + pass + + self.assertRaises( + AssertionError, + lambda: bp.add_url_rule( + '/bar/123', endpoint='bar.123', view_func=foo_foo_foo + ) + ) + + self.assertRaises( + AssertionError, + bp.route('/bar/123', endpoint='bar.123'), + lambda: None + ) + + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/py') + + c = app.test_client() + self.assertEqual(c.get('/py/foo').data, 'bp.foo') + # The rule's din't actually made it through + rv = c.get('/py/bar') + assert rv.status_code == 404 + rv = c.get('/py/bar/123') + assert rv.status_code == 404 class SendfileTestCase(unittest.TestCase): From e853a0f73959ad12cdabc2d74324cdc9329ae794 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:47:50 +0100 Subject: [PATCH 0110/3143] The test client and test_request_context are now both using the same logic internally for creating the environ. Also they use APPLICATION_ROOT now. --- CHANGES | 3 +++ docs/api.rst | 2 +- flask/app.py | 21 +++++++-------------- flask/testing.py | 26 ++++++++++++-------------- tests/flask_tests.py | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/CHANGES b/CHANGES index a451b978..6727e98f 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,9 @@ Relase date to be decided, codename to be chosen. - Added the ``APPLICATION_ROOT`` configuration variable. - Implemented :meth:`~flask.testing.TestClient.session_transaction` to easily modify sessions from the test environment. +- Refactored test client internally. The ``APPLICATION_ROOT`` configuration + variable as well as ``SERVER_NAME`` are now properly used by the test client + as defaults. Version 0.7.3 ------------- diff --git a/docs/api.rst b/docs/api.rst index f4fab86f..1dc25eb1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -223,7 +223,7 @@ Test Client .. currentmodule:: flask.testing -.. autoclass:: TestClient +.. autoclass:: FlaskClient :members: diff --git a/flask/app.py b/flask/app.py index 20cbca52..b4ae64a5 100644 --- a/flask/app.py +++ b/flask/app.py @@ -706,7 +706,7 @@ class Flask(_PackageBoundObject): rv = c.get('/?vodka=42') assert request.args['vodka'] == '42' - See :class:`~flask.testing.TestClient` for more information. + See :class:`~flask.testing.FlaskClient` for more information. .. versionchanged:: 0.4 added support for `with` block usage for the client. @@ -1481,19 +1481,12 @@ class Flask(_PackageBoundObject): :func:`werkzeug.test.EnvironBuilder` for more information, this function accepts the same arguments). """ - from werkzeug.test import create_environ - environ_overrides = kwargs.setdefault('environ_overrides', {}) - if self.config.get('SERVER_NAME'): - server_name = self.config.get('SERVER_NAME') - if ':' not in server_name: - http_host, http_port = server_name, '80' - else: - http_host, http_port = server_name.split(':', 1) - - environ_overrides.setdefault('SERVER_NAME', server_name) - environ_overrides.setdefault('HTTP_HOST', server_name) - environ_overrides.setdefault('SERVER_PORT', http_port) - return self.request_context(create_environ(*args, **kwargs)) + from flask.testing import make_test_environ_builder + builder = make_test_environ_builder(self, *args, **kwargs) + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() def wsgi_app(self, environ, start_response): """The actual WSGI application. This is not implemented in diff --git a/flask/testing.py b/flask/testing.py index dfdaff57..c4d18ca2 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -15,6 +15,17 @@ from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack +def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs): + """Creates a new test builder with some application defaults thrown in.""" + http_host = app.config.get('SERVER_NAME') + app_root = app.config.get('APPLICATION_ROOT') + if base_url is None: + base_url = 'http://%s/' % (http_host or 'localhost') + if app_root: + base_url += app_root.lstrip('/') + return EnvironBuilder(path, base_url, *args, **kwargs) + + class FlaskClient(Client): """Works like a regular Werkzeug test client but has some knowledge about how Flask works to defer the cleanup of the request context stack to the @@ -83,21 +94,8 @@ class FlaskClient(Client): as_tuple = kwargs.pop('as_tuple', False) buffered = kwargs.pop('buffered', False) follow_redirects = kwargs.pop('follow_redirects', False) + builder = make_test_environ_builder(self.application, *args, **kwargs) - builder = EnvironBuilder(*args, **kwargs) - - if self.application.config.get('SERVER_NAME'): - server_name = self.application.config.get('SERVER_NAME') - if ':' not in server_name: - http_host, http_port = server_name, None - else: - http_host, http_port = server_name.split(':', 1) - if builder.base_url == 'http://localhost/': - # Default Generated Base URL - if http_port != None: - builder.host = http_host + ':' + http_port - else: - builder.host = http_host old = _request_ctx_stack.top try: return Client.open(self, builder, diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 62814882..aa8f2cf3 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1042,6 +1042,21 @@ class BasicFunctionalityTestCase(FlaskTestCase): class TestToolsTestCase(FlaskTestCase): + def test_environ_defaults_from_config(self): + app = flask.Flask(__name__) + app.testing = True + app.config['SERVER_NAME'] = 'example.com:1234' + app.config['APPLICATION_ROOT'] = '/foo' + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assertEqual(ctx.request.url, 'http://example.com:1234/foo/') + with app.test_client() as c: + rv = c.get('/') + self.assertEqual(rv.data, 'http://example.com:1234/foo/') + def test_session_transactions(self): app = flask.Flask(__name__) app.testing = True From 8dbd71ef8e16ce64cfcf7c526d584ea2771a28d1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:48:38 +0100 Subject: [PATCH 0111/3143] Added a testcase where SERVER_NAME and APPLICATION_ROOT are not set --- tests/flask_tests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index aa8f2cf3..040ddfaa 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1057,6 +1057,19 @@ class TestToolsTestCase(FlaskTestCase): rv = c.get('/') self.assertEqual(rv.data, 'http://example.com:1234/foo/') + def test_environ_defaults(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assertEqual(ctx.request.url, 'http://localhost/') + with app.test_client() as c: + rv = c.get('/') + self.assertEqual(rv.data, 'http://localhost/') + def test_session_transactions(self): app = flask.Flask(__name__) app.testing = True From 485a6c332b1cd10495f6f3aafe2690e07f01b422 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:49:53 +0100 Subject: [PATCH 0112/3143] Moved testcase for test client context binding to the TestToolsTestCase --- tests/flask_tests.py | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 040ddfaa..1d9daf83 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -153,36 +153,6 @@ class ContextTestCase(FlaskTestCase): else: assert 0, 'expected runtime error' - def test_test_client_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - flask.g.value = 42 - return 'Hello World!' - - @app.route('/other') - def other(): - 1/0 - - with app.test_client() as c: - resp = c.get('/') - assert flask.g.value == 42 - assert resp.data == 'Hello World!' - assert resp.status_code == 200 - - resp = c.get('/other') - assert not hasattr(flask.g, 'value') - assert 'Internal Server Error' in resp.data - assert resp.status_code == 500 - flask.g.value = 23 - - try: - flask.g.value - except (AttributeError, RuntimeError): - pass - else: - raise AssertionError('some kind of exception expected') - class BasicFunctionalityTestCase(FlaskTestCase): @@ -1111,6 +1081,36 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction(): self.assert_(req is flask.request._get_current_object()) + def test_test_client_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + flask.g.value = 42 + return 'Hello World!' + + @app.route('/other') + def other(): + 1/0 + + with app.test_client() as c: + resp = c.get('/') + assert flask.g.value == 42 + assert resp.data == 'Hello World!' + assert resp.status_code == 200 + + resp = c.get('/other') + assert not hasattr(flask.g, 'value') + assert 'Internal Server Error' in resp.data + assert resp.status_code == 500 + flask.g.value = 23 + + try: + flask.g.value + except (AttributeError, RuntimeError): + pass + else: + raise AssertionError('some kind of exception expected') + class InstanceTestCase(FlaskTestCase): From d3ca55177a9f39de526143c59edc049d28f4424c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 20:56:43 +0100 Subject: [PATCH 0113/3143] Updated the docs and examples to non-failing teardown handlers --- docs/patterns/sqlite3.rst | 10 +++++++++- docs/reqcontext.rst | 10 +++++++++- docs/upgrading.rst | 3 ++- examples/flaskr/flaskr.py | 3 ++- examples/minitwit/minitwit.py | 3 ++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index bc471f66..0d02e465 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -24,7 +24,15 @@ So here is a simple example of how you can use SQLite 3 with Flask:: @app.teardown_request def teardown_request(exception): - g.db.close() + 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. Connect on Demand ----------------- diff --git a/docs/reqcontext.rst b/docs/reqcontext.rst index 3b49e1d5..0249b88e 100644 --- a/docs/reqcontext.rst +++ b/docs/reqcontext.rst @@ -131,7 +131,9 @@ understand what is actually happening. The new behavior is quite simple: 4. At the end of the request the :meth:`~flask.Flask.teardown_request` functions are executed. This always happens, even in case of an - unhandled exception down the road. + unhandled exception down the road or if a before-request handler was + not executed yet or at all (for example in test environments sometimes + you might want to not execute before-request callbacks). Now what happens on errors? In production mode if an exception is not caught, the 500 internal server handler is called. In development mode @@ -183,6 +185,12 @@ It's easy to see the behavior from the command line: this runs after request >>> +Keep in mind that teardown callbacks are always executed, even if +before-request callbacks were not executed yet but an exception happened. +Certain parts of the test system might also temporarily create a request +context without calling the before-request handlers. Make sure to write +your teardown-request handlers in a way that they will never fail. + .. _notes-on-proxies: Notes On Proxies diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 13b5be71..b318292c 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -142,7 +142,8 @@ You are now encouraged to use this instead:: @app.teardown_request def after_request(exception): - g.db.close() + if hasattr(g, 'db'): + g.db.close() On the upside this change greatly improves the internal code flow and makes it easier to customize the dispatching and error handling. This diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 361c1aee..6f9b06fc 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -50,7 +50,8 @@ def before_request(): @app.teardown_request def teardown_request(exception): """Closes the database again at the end of the request.""" - g.db.close() + if hasattr(g, 'db'): + g.db.close() @app.route('/') diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index f7c700d3..fee023fd 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -85,7 +85,8 @@ def before_request(): @app.teardown_request def teardown_request(exception): """Closes the database again at the end of the request.""" - g.db.close() + if hasattr(g, 'db'): + g.db.close() @app.route('/') From a43f73c75c169de86dde372d6ecc42019773271f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Neuh=C3=A4user?= Date: Fri, 26 Aug 2011 10:13:54 +0300 Subject: [PATCH 0114/3143] s/Ify ou/If you/ typo --- docs/testing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index ed44e5a2..1e00fe80 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -281,7 +281,7 @@ Accessing and Modifying Sessions .. versionadded:: 0.8 Sometimes it can be very helpful to access or modify the sessions from the -test client. Generally there are two ways for this. Ify ou just want to +test client. Generally there are two ways for this. If you just want to ensure that a session has certain keys set to certain values you can just keep the context around and access :data:`flask.session`:: From 314f9201ab133ca7b78f219937371223e14ee32a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 21:16:06 +0100 Subject: [PATCH 0115/3143] Updated instance path documentation to explain the $PREFIX lookup --- docs/config.rst | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 65e5064a..df31aba0 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -299,25 +299,37 @@ With Flask 0.8 a new attribute was introduced: version control and be deployment specific. It's the perfect place to drop things that either change at runtime or configuration files. -To make it easier to put this folder into an ignore list for your version -control system it's called ``instance`` and placed directly next to your -package or module by default. This path can be overridden by specifying -the `instance_path` parameter to your application:: +You can either explicitly provide the path of the instance folder when +creating the Flask application or you can let Flask autodetect the +instance folder. For explicit configuration use the `instance_path` +parameter:: app = Flask(__name__, instance_path='/path/to/instance/folder') -Default locations:: +Please keep in mind that this path *must* be absolute when provided. + +If the `instance_path` parameter is not provided the following default +locations are used: + +- Uninstalled module:: - Module situation: /myapp.py /instance - Package situation: +- Uninstalled package:: + /myapp /__init__.py /instance -Please keep in mind that this path *must* be absolute when provided. +- Installed module or package:: + + $PREFIX/lib/python2.X/site-packages/myapp + $PREFIX/var/myapp-instance + + ``$PREFIX`` is the prefix of your Python installation. This can be + ``/usr`` or the path to your virtualenv. You can print the value of + ``sys.prefix`` to see what the prefix is set to. Since the config object provided loading of configuration files from relative filenames we made it possible to change the loading via filenames From 8340d3c9f5cb38f23dd16d7d28aa643a121c4236 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 21:49:50 +0100 Subject: [PATCH 0116/3143] Updated docstring on make_response --- flask/helpers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index a260b03f..d8f7ac63 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -145,6 +145,13 @@ def make_response(*args): response = make_response(render_template('not_found.html'), 404) + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + Internally this function does the following things: - if no arguments are passed, it creates a new response argument From ef0f626f0a916fdced42e05aa2f58dede6c660fd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2011 22:09:48 +0100 Subject: [PATCH 0117/3143] Added flask.views.View.decorators to automatically decorate class based views. --- CHANGES | 2 ++ flask/views.py | 35 +++++++++++++++++++++++++++++++++++ tests/flask_tests.py | 21 +++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/CHANGES b/CHANGES index 6727e98f..fa8c00bf 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,8 @@ Relase date to be decided, codename to be chosen. - Refactored test client internally. The ``APPLICATION_ROOT`` configuration 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. Version 0.7.3 ------------- diff --git a/flask/views.py b/flask/views.py index 9a185570..2fe34622 100644 --- a/flask/views.py +++ b/flask/views.py @@ -30,10 +30,38 @@ class View(object): return 'Hello %s!' % name app.add_url_rule('/hello/', view_func=MyView.as_view('myview')) + + When you want to decorate a pluggable view you will have to either do that + when the view function is created (by wrapping the return value of + :meth:`as_view`) or you can use the :attr:`decorators` attribute:: + + class SecretView(View): + methods = ['GET'] + decorators = [superuser_required] + + def dispatch_request(self): + ... + + The decorators stored in the decorators list are applied one after another + when the view function is created. Note that you can *not* use the class + based decorators since those would decorate the view class and not the + generated view function! """ + #: A for which methods this pluggable view can handle. methods = None + #: The canonical way to decorate class based views is to decorate the + #: return value of as_view(). However since this moves parts of the + #: logic from the class declaration to the place where it's hooked + #: into the routing system. + #: + #: You can place one or more decorators in this list and whenever the + #: view function is created the result is automatically decorated. + #: + #: .. versionadded:: 0.8 + decorators = [] + def dispatch_request(self): """Subclasses have to override this method to implement the actual view functionc ode. This method is called with all @@ -54,6 +82,13 @@ class View(object): def view(*args, **kwargs): self = view.view_class(*class_args, **class_kwargs) return self.dispatch_request(*args, **kwargs) + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + 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 # view this thing came from, secondly it's also used for instanciating diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 1d9daf83..dcc3e1a0 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -2258,6 +2258,27 @@ class ViewTestCase(FlaskTestCase): meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) + def test_view_decorators(self): + app = flask.Flask(__name__) + + def add_x_parachute(f): + def new_function(*args, **kwargs): + resp = flask.make_response(f(*args, **kwargs)) + resp.headers['X-Parachute'] = 'awesome' + return resp + return new_function + + class Index(flask.views.View): + decorators = [add_x_parachute] + def dispatch_request(self): + return 'Awesome' + + app.add_url_rule('/', view_func=Index.as_view('index')) + c = app.test_client() + rv = c.get('/') + self.assertEqual(rv.headers['X-Parachute'], 'awesome') + self.assertEqual(rv.data, 'Awesome') + class DeprecationsTestCase(FlaskTestCase): From 85ed1bf05834f484ce7dfdc37e11b89eec8cee65 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 08:32:36 +0100 Subject: [PATCH 0118/3143] Mentioned View.decorators in the views docs --- docs/views.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/views.rst b/docs/views.rst index fc22d1d7..10ddb57d 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -135,3 +135,24 @@ easily do that. Each HTTP method maps to a function with the same name That way you also don't have to provide the :attr:`~flask.views.View.methods` attribute. It's automatically set based on the methods defined in the class. + +Decorating Views +---------------- + +Since the view class itself is not the view function that is added to the +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')) + 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] + +Due to the implicit self from the caller's perspective you cannot use +regular view decorators on the individual methods of the view however, +keep this in mind. From 4cb6eea8f1e1edc707874f72cbca43a0f698ec7d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:21:26 +0100 Subject: [PATCH 0119/3143] Split up testsuite and moved it to flask.testsuite. This fixes #246 --- flask/testsuite/__init__.py | 121 + flask/testsuite/basic.py | 992 +++++++ flask/testsuite/blueprints.py | 423 +++ flask/testsuite/config.py | 177 ++ flask/testsuite/deprecations.py | 38 + flask/testsuite/examples.py | 38 + flask/testsuite/helpers.py | 295 +++ flask/testsuite/signals.py | 103 + {tests => flask/testsuite}/static/index.html | 0 .../testsuite}/templates/_macro.html | 0 .../templates/context_template.html | 0 .../templates/escaping_template.html | 0 {tests => flask/testsuite}/templates/mail.txt | 0 .../testsuite}/templates/nested/nested.txt | 0 .../testsuite}/templates/simple_template.html | 0 .../testsuite}/templates/template_filter.html | 0 flask/testsuite/templating.py | 141 + .../test_apps}/blueprintapp/__init__.py | 0 .../test_apps}/blueprintapp/apps/__init__.py | 0 .../blueprintapp/apps/admin/__init__.py | 0 .../apps/admin/static/css/test.css | 0 .../blueprintapp/apps/admin/static/test.txt | 0 .../apps/admin/templates/admin/index.html | 0 .../blueprintapp/apps/frontend/__init__.py | 0 .../frontend/templates/frontend/index.html | 0 .../testsuite/test_apps/config_module_app.py | 4 + .../test_apps/config_package_app/__init__.py | 4 + .../test_apps}/moduleapp/__init__.py | 0 .../test_apps}/moduleapp/apps/__init__.py | 0 .../moduleapp/apps/admin/__init__.py | 0 .../moduleapp/apps/admin/static/css/test.css | 0 .../moduleapp/apps/admin/static/test.txt | 0 .../moduleapp/apps/admin/templates/index.html | 0 .../moduleapp/apps/frontend/__init__.py | 0 .../apps/frontend/templates/index.html | 0 .../subdomaintestmodule/__init__.py | 0 .../subdomaintestmodule/static/hello.txt | 0 flask/testsuite/testing.py | 121 + flask/testsuite/views.py | 117 + {tests => scripts}/flaskext_test.py | 0 setup.py | 8 +- tests/flask_tests.py | 2329 ----------------- 42 files changed, 2575 insertions(+), 2336 deletions(-) create mode 100644 flask/testsuite/__init__.py create mode 100644 flask/testsuite/basic.py create mode 100644 flask/testsuite/blueprints.py create mode 100644 flask/testsuite/config.py create mode 100644 flask/testsuite/deprecations.py create mode 100644 flask/testsuite/examples.py create mode 100644 flask/testsuite/helpers.py create mode 100644 flask/testsuite/signals.py rename {tests => flask/testsuite}/static/index.html (100%) rename {tests => flask/testsuite}/templates/_macro.html (100%) rename {tests => flask/testsuite}/templates/context_template.html (100%) rename {tests => flask/testsuite}/templates/escaping_template.html (100%) rename {tests => flask/testsuite}/templates/mail.txt (100%) rename {tests => flask/testsuite}/templates/nested/nested.txt (100%) rename {tests => flask/testsuite}/templates/simple_template.html (100%) rename {tests => flask/testsuite}/templates/template_filter.html (100%) create mode 100644 flask/testsuite/templating.py rename {tests => flask/testsuite/test_apps}/blueprintapp/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/static/css/test.css (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/static/test.txt (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/admin/templates/admin/index.html (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/frontend/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/blueprintapp/apps/frontend/templates/frontend/index.html (100%) create mode 100644 flask/testsuite/test_apps/config_module_app.py create mode 100644 flask/testsuite/test_apps/config_package_app/__init__.py rename {tests => flask/testsuite/test_apps}/moduleapp/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/static/css/test.css (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/static/test.txt (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/admin/templates/index.html (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/frontend/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/moduleapp/apps/frontend/templates/index.html (100%) rename {tests => flask/testsuite/test_apps}/subdomaintestmodule/__init__.py (100%) rename {tests => flask/testsuite/test_apps}/subdomaintestmodule/static/hello.txt (100%) create mode 100644 flask/testsuite/testing.py create mode 100644 flask/testsuite/views.py rename {tests => scripts}/flaskext_test.py (100%) delete mode 100644 tests/flask_tests.py diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py new file mode 100644 index 00000000..8df7a7fd --- /dev/null +++ b/flask/testsuite/__init__.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite + ~~~~~~~~~~~~~~~ + + Tests Flask itself. The majority of Flask is already tested + as part of Werkzeug. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +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 + + +def add_to_path(path): + def _samefile(x, y): + try: + return os.path.samefile(x, y) + except (IOError, OSError): + return False + for entry in sys.path: + try: + if os.path.samefile(path, entry): + return + except (OSError, IOError): + pass + sys.path.append(path) + + +def setup_paths(): + add_to_path(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps'))) + + +def iter_suites(): + for module in find_modules(__name__): + mod = import_string(module) + if hasattr(mod, 'suite'): + yield mod.suite() + + +@contextmanager +def catch_warnings(): + """Catch warnings in a with block in a list""" + # make sure deprecation warnings are active in tests + warnings.simplefilter('default', category=DeprecationWarning) + + filters = warnings.filters + warnings.filters = filters[:] + old_showwarning = warnings.showwarning + log = [] + def showwarning(message, category, filename, lineno, file=None, line=None): + log.append(locals()) + try: + warnings.showwarning = showwarning + yield log + finally: + warnings.filters = filters + warnings.showwarning = old_showwarning + + +@contextmanager +def catch_stderr(): + """Catch stderr in a StringIO""" + old_stderr = sys.stderr + sys.stderr = rv = StringIO() + try: + yield rv + finally: + sys.stderr = old_stderr + + +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') + for entry in log: + self.assert_('Modules are deprecated' in str(entry['message'])) + return update_wrapper(new_f, f) + + +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) + + def setup(self): + pass + + def teardown(self): + pass + + def setUp(self): + self.setup() + + def tearDown(self): + unittest.TestCase.tearDown(self) + self.ensure_clean_request_context() + self.teardown() + + def assert_equal(self, x, y): + return self.assertEqual(x, y) + + +def suite(): + setup_paths() + suite = unittest.TestSuite() + for other_suite in iter_suites(): + suite.addTest(other_suite) + return suite diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py new file mode 100644 index 00000000..c55881e5 --- /dev/null +++ b/flask/testsuite/basic.py @@ -0,0 +1,992 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.basic + ~~~~~~~~~~~~~~~~~~~~~ + + The basic functionality. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import re +import flask +import unittest +from datetime import datetime +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 + + +class BasicFunctionalityTestCase(FlaskTestCase): + + def test_options_work(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + assert rv.data == '' + + def test_options_on_multiple_rules(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + @app.route('/', methods=['PUT']) + def index_put(): + return 'Aha!' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + def test_options_handling_disabled(self): + app = flask.Flask(__name__) + def index(): + return 'Hello World!' + index.provide_automatic_options = False + app.route('/')(index) + rv = app.test_client().open('/', method='OPTIONS') + assert rv.status_code == 405 + + app = flask.Flask(__name__) + def index2(): + return 'Hello World!' + index2.provide_automatic_options = True + app.route('/', methods=['OPTIONS'])(index2) + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['OPTIONS'] + + def test_request_dispatching(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.request.method + @app.route('/more', methods=['GET', 'POST']) + def more(): + return flask.request.method + + c = app.test_client() + assert c.get('/').data == 'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == 'POST' + assert c.get('/more').data == 'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + def test_url_mapping(self): + app = flask.Flask(__name__) + def index(): + return flask.request.method + def more(): + return flask.request.method + + app.add_url_rule('/', 'index', index) + app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) + + c = app.test_client() + assert c.get('/').data == 'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == 'POST' + assert c.get('/more').data == 'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + + def test_werkzeug_routing(self): + from werkzeug.routing import Submount, Rule + app = flask.Flask(__name__) + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + def bar(): + return 'bar' + def index(): + return 'index' + app.view_functions['bar'] = bar + app.view_functions['index'] = index + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + def test_endpoint_decorator(self): + from werkzeug.routing import Submount, Rule + app = flask.Flask(__name__) + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + + @app.endpoint('bar') + def bar(): + return 'bar' + + @app.endpoint('index') + def index(): + return 'index' + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + def test_session(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + @app.route('/set', methods=['POST']) + def set(): + flask.session['value'] = flask.request.form['value'] + return 'value set' + @app.route('/get') + def get(): + return flask.session['value'] + + c = app.test_client() + assert c.post('/set', data={'value': '42'}).data == 'value set' + assert c.get('/get').data == '42' + + def test_session_using_server_name(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='example.com' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + def test_session_using_server_name_and_port(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='example.com:8080' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/') + assert 'domain=.example.com' in rv.headers['set-cookie'].lower() + assert 'httponly' in rv.headers['set-cookie'].lower() + + def test_session_using_application_root(self): + class PrefixPathMiddleware(object): + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + def __call__(self, environ, start_response): + environ['SCRIPT_NAME'] = self.prefix + return self.app(environ, start_response) + + app = flask.Flask(__name__) + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') + app.config.update( + SECRET_KEY='foo', + APPLICATION_ROOT='/bar' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + return 'Hello World' + rv = app.test_client().get('/', 'http://example.com:8080/') + assert 'path=/bar' in rv.headers['set-cookie'].lower() + + def test_missing_session(self): + app = flask.Flask(__name__) + def expect_exception(f, *args, **kwargs): + try: + f(*args, **kwargs) + except RuntimeError, e: + assert e.args and 'session is unavailable' in e.args[0] + else: + assert False, 'expected exception' + with app.test_request_context(): + assert flask.session.get('missing_key') is None + expect_exception(flask.session.__setitem__, 'foo', 42) + expect_exception(flask.session.pop, 'foo') + + def test_session_expiration(self): + permanent = True + app = flask.Flask(__name__) + app.secret_key = 'testkey' + @app.route('/') + def index(): + flask.session['test'] = 42 + flask.session.permanent = permanent + return '' + + @app.route('/test') + def test(): + return unicode(flask.session.permanent) + + client = app.test_client() + rv = client.get('/') + assert '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 + assert expires.year == expected.year + assert expires.month == expected.month + assert expires.day == expected.day + + rv = client.get('/test') + assert rv.data == 'True' + + permanent = False + rv = app.test_client().get('/') + assert 'set-cookie' in rv.headers + match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) + assert match is None + + def test_flashes(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + with app.test_request_context(): + assert not flask.session.modified + flask.flash('Zap') + flask.session.modified = False + flask.flash('Zip') + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + + def test_extended_flashing(self): + app = flask.Flask(__name__) + app.secret_key = 'testkey' + + @app.route('/') + def index(): + flask.flash(u'Hello World') + flask.flash(u'Hello World', 'error') + flask.flash(flask.Markup(u'Testing'), 'warning') + return '' + + @app.route('/test') + def test(): + messages = flask.get_flashed_messages(with_categories=True) + assert len(messages) == 3 + assert messages[0] == ('message', u'Hello World') + assert messages[1] == ('error', u'Hello World') + assert messages[2] == ('warning', flask.Markup(u'Testing')) + return '' + messages = flask.get_flashed_messages() + assert len(messages) == 3 + assert messages[0] == u'Hello World' + assert messages[1] == u'Hello World' + assert messages[2] == flask.Markup(u'Testing') + + c = app.test_client() + c.get('/') + c.get('/test') + + def test_request_processing(self): + app = flask.Flask(__name__) + evts = [] + @app.before_request + def before_request(): + evts.append('before') + @app.after_request + def after_request(response): + response.data += '|after' + evts.append('after') + return response + @app.route('/') + def index(): + assert 'before' in evts + assert 'after' not in evts + return 'request' + assert 'after' not in evts + rv = app.test_client().get('/').data + assert 'after' in evts + assert rv == 'request|after' + + def test_teardown_request_handler(self): + called = [] + app = flask.Flask(__name__) + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + @app.route('/') + def root(): + return "Response" + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert 'Response' in rv.data + assert len(called) == 1 + + def test_teardown_request_handler_debug_mode(self): + called = [] + app = flask.Flask(__name__) + app.testing = True + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + @app.route('/') + def root(): + return "Response" + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert 'Response' in rv.data + assert len(called) == 1 + + def test_teardown_request_handler_error(self): + called = [] + app = flask.Flask(__name__) + @app.teardown_request + def teardown_request1(exc): + assert type(exc) == ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError + except: + pass + @app.teardown_request + def teardown_request2(exc): + assert type(exc) == ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError + except: + pass + @app.route('/') + def fails(): + 1/0 + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + assert len(called) == 2 + + def test_before_after_request_order(self): + called = [] + app = flask.Flask(__name__) + @app.before_request + def before1(): + called.append(1) + @app.before_request + def before2(): + called.append(2) + @app.after_request + def after1(response): + called.append(4) + return response + @app.after_request + def after2(response): + called.append(3) + return response + @app.teardown_request + def finish1(exc): + called.append(6) + @app.teardown_request + def finish2(exc): + called.append(5) + @app.route('/') + def index(): + return '42' + rv = app.test_client().get('/') + assert rv.data == '42' + assert called == [1, 2, 3, 4, 5, 6] + + def test_error_handling(self): + app = flask.Flask(__name__) + @app.errorhandler(404) + def not_found(e): + return 'not found', 404 + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + @app.route('/') + def index(): + flask.abort(404) + @app.route('/error') + def error(): + 1 // 0 + c = app.test_client() + rv = c.get('/') + assert rv.status_code == 404 + assert rv.data == 'not found' + rv = c.get('/error') + assert rv.status_code == 500 + assert 'internal server error' == rv.data + + def test_before_request_and_routing_errors(self): + app = flask.Flask(__name__) + @app.before_request + def attach_something(): + flask.g.something = 'value' + @app.errorhandler(404) + def return_something(error): + return flask.g.something, 404 + rv = app.test_client().get('/') + assert rv.status_code == 404 + assert rv.data == 'value' + + def test_user_error_handling(self): + class MyException(Exception): + pass + + app = flask.Flask(__name__) + @app.errorhandler(MyException) + def handle_my_exception(e): + assert isinstance(e, MyException) + return '42' + @app.route('/') + def index(): + raise MyException() + + c = app.test_client() + assert c.get('/').data == '42' + + def test_trapping_of_bad_request_key_errors(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/fail') + def fail(): + flask.request.form['missing_key'] + c = app.test_client() + assert c.get('/fail').status_code == 400 + + app.config['TRAP_BAD_REQUEST_ERRORS'] = True + c = app.test_client() + try: + c.get('/fail') + except KeyError, e: + assert isinstance(e, BadRequest) + else: + self.fail('Expected exception') + + def test_trapping_of_all_http_exceptions(self): + app = flask.Flask(__name__) + app.testing = True + app.config['TRAP_HTTP_EXCEPTIONS'] = True + @app.route('/fail') + def fail(): + flask.abort(404) + + c = app.test_client() + try: + c.get('/fail') + except NotFound, e: + pass + else: + self.fail('Expected exception') + + def test_enctype_debug_helper(self): + from flask.debughelpers import DebugFilesKeyError + app = flask.Flask(__name__) + app.debug = True + @app.route('/fail', methods=['POST']) + def index(): + return flask.request.files['foo'].filename + + # with statement is important because we leave an exception on the + # stack otherwise and we want to ensure that this is not the case + # to not negatively affect other tests. + with app.test_client() as c: + try: + c.post('/fail', data={'foo': 'index.txt'}) + except DebugFilesKeyError, e: + assert 'no file contents were transmitted' in str(e) + assert 'This was submitted: "index.txt"' in str(e) + 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() + assert buffer == [] + ctx.pop() + assert buffer == [None] + + def test_response_creation(self): + app = flask.Flask(__name__) + @app.route('/unicode') + def from_unicode(): + return u'Hällo Wörld' + @app.route('/string') + def from_string(): + return u'Hällo Wörld'.encode('utf-8') + @app.route('/args') + def from_tuple(): + return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' + c = app.test_client() + assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') + assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') + rv = c.get('/args') + assert rv.data == 'Meh' + assert rv.headers['X-Foo'] == 'Testing' + assert rv.status_code == 400 + assert rv.mimetype == 'text/plain' + + def test_make_response(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.make_response() + assert rv.status_code == 200 + assert rv.data == '' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('Awesome') + assert rv.status_code == 200 + assert rv.data == 'Awesome' + assert rv.mimetype == 'text/html' + + rv = flask.make_response('W00t', 404) + assert rv.status_code == 404 + assert rv.data == 'W00t' + assert rv.mimetype == 'text/html' + + def test_url_generation(self): + app = flask.Flask(__name__) + @app.route('/hello/', methods=['POST']) + def hello(): + pass + with app.test_request_context(): + assert flask.url_for('hello', name='test x') == '/hello/test%20x' + assert flask.url_for('hello', name='test x', _external=True) \ + == 'http://localhost/hello/test%20x' + + def test_custom_converters(self): + from werkzeug.routing import BaseConverter + class ListConverter(BaseConverter): + def to_python(self, value): + return value.split(',') + def to_url(self, value): + base_to_url = super(ListConverter, self).to_url + return ','.join(base_to_url(x) for x in value) + app = flask.Flask(__name__) + app.url_map.converters['list'] = ListConverter + @app.route('/') + def index(args): + return '|'.join(args) + c = app.test_client() + assert c.get('/1,2,3').data == '1|2|3' + + def test_static_files(self): + app = flask.Flask(__name__) + rv = app.test_client().get('/static/index.html') + assert rv.status_code == 200 + assert rv.data.strip() == '

Hello World!

' + with app.test_request_context(): + assert flask.url_for('static', filename='index.html') \ + == '/static/index.html' + + def test_none_response(self): + app = flask.Flask(__name__) + @app.route('/') + def test(): + return None + try: + app.test_client().get('/') + except ValueError, e: + assert str(e) == 'View function did not return a response' + pass + else: + assert "Expected ValueError" + + def test_request_locals(self): + 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('/'): + assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' + + with app.test_request_context('/'): + assert 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: + assert isinstance(e, ValueError) + assert str(e) == "the server name provided " + \ + "('localhost.localdomain:5000') does not match the " + \ + "server name from the WSGI environment ('localhost')", str(e) + + 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( + SERVER_NAME='localhost.localdomain:5000' + ) + @app.route('/') + def index(): + return 'Foo' + + @app.route('/', subdomain='foo') + def subdomain(): + return 'Foo SubDomain' + + try: + rv = app.test_client().get('/') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + rv = app.test_client().get('/', 'http://localhost.localdomain:5000') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + rv = app.test_client().get('/', 'https://localhost.localdomain:5000') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + rv = app.test_client().get('/', 'https://localhost.localdomain') + assert rv.data == 'Foo' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + try: + app.config.update(SERVER_NAME='localhost.localdomain:443') + rv = app.test_client().get('/', 'https://localhost.localdomain') + assert rv.data == 'Foo' + except ValueError, e: + assert str(e) == "the server name provided " + \ + "('localhost.localdomain:443') does not match the " + \ + "server name from the WSGI environment ('localhost.localdomain')", str(e) + + try: + app.config.update(SERVER_NAME='localhost.localdomain') + app.test_client().get('/', 'http://foo.localhost') + except ValueError, e: + assert str(e) == "the server name provided " + \ + "('localhost.localdomain') does not match the " + \ + "server name from the WSGI environment ('foo.localhost')", str(e) + + try: + rv = app.test_client().get('/', 'http://foo.localhost.localdomain') + assert rv.data == 'Foo SubDomain' + except ValueError, e: + raise ValueError( + "No ValueError exception should have been raised \"%s\"" % e + ) + + def test_exception_propagation(self): + def apprunner(configkey): + app = flask.Flask(__name__) + @app.route('/') + def index(): + 1/0 + c = app.test_client() + if config_key is not None: + app.config[config_key] = True + try: + resp = c.get('/') + except Exception: + pass + else: + self.fail('expected exception') + else: + assert c.get('/').status_code == 500 + + # we have to run this test in an isolated thread because if the + # debug flag is set to true and an exception happens the context is + # not torn down. This causes other tests that run after this fail + # when they expect no exception on the stack. + for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None: + t = Thread(target=apprunner, args=(config_key,)) + t.start() + t.join() + + def test_max_content_length(self): + app = flask.Flask(__name__) + app.config['MAX_CONTENT_LENGTH'] = 64 + @app.before_request + def always_first(): + flask.request.form['myfile'] + assert False + @app.route('/accept', methods=['POST']) + def accept_file(): + flask.request.form['myfile'] + assert False + @app.errorhandler(413) + def catcher(error): + return '42' + + c = app.test_client() + rv = c.post('/accept', data={'myfile': 'foo' * 100}) + assert rv.data == '42' + + def test_url_processors(self): + app = flask.Flask(__name__) + + @app.url_defaults + def add_language_code(endpoint, values): + if flask.g.lang_code is not None and \ + app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values.setdefault('lang_code', flask.g.lang_code) + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code', None) + + @app.route('//') + def index(): + return flask.url_for('about') + + @app.route('//about') + def about(): + return flask.url_for('something_else') + + @app.route('/foo') + def something_else(): + return flask.url_for('about', lang_code='en') + + 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') + + def test_debug_mode_complains_after_first_request(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/') + def index(): + return 'Awesome' + self.assert_(not app.got_first_request) + self.assert_equal(app.test_client().get('/').data, 'Awesome') + try: + @app.route('/foo') + def broken(): + return 'Meh' + except AssertionError, e: + self.assert_('A setup function was called' in str(e)) + else: + self.fail('Expected exception') + + app.debug = False + @app.route('/foo') + def working(): + return 'Meh' + self.assert_equal(app.test_client().get('/foo').data, 'Meh') + self.assert_(app.got_first_request) + + def test_before_first_request_functions(self): + got = [] + app = flask.Flask(__name__) + @app.before_first_request + def foo(): + got.append(42) + c = app.test_client() + c.get('/') + self.assert_equal(got, [42]) + c.get('/') + self.assert_equal(got, [42]) + self.assert_(app.got_first_request) + + def test_routing_redirect_debugging(self): + app = flask.Flask(__name__) + app.debug = True + @app.route('/foo/', methods=['GET', 'POST']) + def foo(): + return 'success' + with app.test_client() as c: + try: + c.post('/foo', data={}) + except AssertionError, 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)) + else: + self.fail('Expected exception') + + rv = c.get('/foo', data={}, follow_redirects=True) + self.assert_equal(rv.data, 'success') + + app.debug = False + with app.test_client() as c: + rv = c.post('/foo', data={}, follow_redirects=True) + self.assert_equal(rv.data, 'success') + + +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'): + assert index() == 'Hello World!' + with app.test_request_context('/meh'): + assert meh() == 'http://localhost/meh' + assert flask._request_ctx_stack.top is None + + def test_context_test(self): + app = flask.Flask(__name__) + assert not flask.request + assert not flask.has_request_context() + ctx = app.test_request_context() + ctx.push() + try: + assert flask.request + 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() + assert index() == 'Hello World!' + ctx.pop() + try: + index() + except RuntimeError: + pass + else: + assert 0, 'expected runtime error' + + +class SubdomainTestCase(FlaskTestCase): + + def test_basic_support(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + @app.route('/') + def normal_index(): + return 'normal index' + @app.route('/', subdomain='test') + def test_index(): + return 'test index' + + c = app.test_client() + rv = c.get('/', 'http://localhost/') + assert rv.data == 'normal index' + + rv = c.get('/', 'http://test.localhost/') + assert rv.data == 'test index' + + @emits_module_deprecation_warning + def test_module_static_path_subdomain(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'example.com' + from subdomaintestmodule import mod + app.register_module(mod) + c = app.test_client() + rv = c.get('/static/hello.txt', 'http://foo.example.com/') + assert rv.data.strip() == 'Hello Subdomain' + + def test_subdomain_matching(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost' + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + c = app.test_client() + rv = c.get('/', 'http://mitsuhiko.localhost/') + assert rv.data == 'index for mitsuhiko' + + @emits_module_deprecation_warning + def test_module_subdomain_support(self): + app = flask.Flask(__name__) + mod = flask.Module(__name__, 'test', subdomain='testing') + app.config['SERVER_NAME'] = 'localhost' + + @mod.route('/test') + def test(): + return 'Test' + + @mod.route('/outside', subdomain='xtesting') + def bar(): + return 'Outside' + + app.register_module(mod) + + c = app.test_client() + rv = c.get('/test', 'http://testing.localhost/') + assert rv.data == 'Test' + rv = c.get('/outside', 'http://xtesting.localhost/') + assert rv.data == 'Outside' + + +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/blueprints.py b/flask/testsuite/blueprints.py new file mode 100644 index 00000000..93122ac5 --- /dev/null +++ b/flask/testsuite/blueprints.py @@ -0,0 +1,423 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.blueprints + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Blueprints (and currently modules) + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +import warnings +from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning +from werkzeug.exceptions import NotFound +from jinja2 import TemplateNotFound + + +# import moduleapp here because it uses deprecated features and we don't +# want to see the warnings +warnings.simplefilter('ignore', DeprecationWarning) +from moduleapp import app as moduleapp +warnings.simplefilter('default', DeprecationWarning) + + +class ModuleTestCase(FlaskTestCase): + + @emits_module_deprecation_warning + def test_basic_module(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin', url_prefix='/admin') + @admin.route('/') + def admin_index(): + return 'admin index' + @admin.route('/login') + def admin_login(): + return 'admin login' + @admin.route('/logout') + def admin_logout(): + return 'admin logout' + @app.route('/') + def index(): + return 'the index' + app.register_module(admin) + c = app.test_client() + assert c.get('/').data == 'the index' + assert c.get('/admin/').data == 'admin index' + assert c.get('/admin/login').data == 'admin login' + assert c.get('/admin/logout').data == 'admin logout' + + @emits_module_deprecation_warning + def test_default_endpoint_name(self): + app = flask.Flask(__name__) + mod = flask.Module(__name__, 'frontend') + def index(): + return 'Awesome' + mod.add_url_rule('/', view_func=index) + app.register_module(mod) + rv = app.test_client().get('/') + assert rv.data == 'Awesome' + with app.test_request_context(): + assert flask.url_for('frontend.index') == '/' + + @emits_module_deprecation_warning + def test_request_processing(self): + catched = [] + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin', url_prefix='/admin') + @admin.before_request + def before_admin_request(): + catched.append('before-admin') + @admin.after_request + def after_admin_request(response): + catched.append('after-admin') + return response + @admin.route('/') + def admin_index(): + return 'the admin' + @app.before_request + def before_request(): + catched.append('before-app') + @app.after_request + def after_request(response): + catched.append('after-app') + return response + @app.route('/') + def index(): + return 'the index' + app.register_module(admin) + c = app.test_client() + + assert c.get('/').data == 'the index' + assert catched == ['before-app', 'after-app'] + del catched[:] + + assert c.get('/admin/').data == 'the admin' + assert catched == ['before-app', 'before-admin', + 'after-admin', 'after-app'] + + @emits_module_deprecation_warning + def test_context_processors(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin', url_prefix='/admin') + @app.context_processor + def inject_all_regualr(): + return {'a': 1} + @admin.context_processor + def inject_admin(): + return {'b': 2} + @admin.app_context_processor + def inject_all_module(): + return {'c': 3} + @app.route('/') + def index(): + return flask.render_template_string('{{ a }}{{ b }}{{ c }}') + @admin.route('/') + def admin_index(): + return flask.render_template_string('{{ a }}{{ b }}{{ c }}') + app.register_module(admin) + c = app.test_client() + assert c.get('/').data == '13' + assert c.get('/admin/').data == '123' + + @emits_module_deprecation_warning + def test_late_binding(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin') + @admin.route('/') + def index(): + return '42' + app.register_module(admin, url_prefix='/admin') + assert app.test_client().get('/admin/').data == '42' + + @emits_module_deprecation_warning + def test_error_handling(self): + app = flask.Flask(__name__) + admin = flask.Module(__name__, 'admin') + @admin.app_errorhandler(404) + def not_found(e): + return 'not found', 404 + @admin.app_errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + @admin.route('/') + def index(): + flask.abort(404) + @admin.route('/error') + def error(): + 1 // 0 + app.register_module(admin) + c = app.test_client() + rv = c.get('/') + assert rv.status_code == 404 + assert rv.data == 'not found' + rv = c.get('/error') + assert rv.status_code == 500 + assert 'internal server error' == rv.data + + def test_templates_and_static(self): + app = moduleapp + app.testing = True + c = app.test_client() + + rv = c.get('/') + assert rv.data == 'Hello from the Frontend' + rv = c.get('/admin/') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/index2') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/static/test.txt') + assert rv.data.strip() == 'Admin File' + rv = c.get('/admin/static/css/test.css') + assert rv.data.strip() == '/* nested file */' + + with app.test_request_context(): + assert flask.url_for('admin.static', filename='test.txt') \ + == '/admin/static/test.txt' + + with app.test_request_context(): + try: + flask.render_template('missing.html') + except TemplateNotFound, e: + assert e.name == 'missing.html' + else: + assert 0, 'expected exception' + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template('nested/nested.txt') == 'I\'m nested' + + def test_safe_access(self): + app = moduleapp + + with app.test_request_context(): + f = app.view_functions['admin.static'] + + try: + f('/etc/passwd') + except NotFound: + pass + else: + assert 0, 'expected exception' + try: + f('../__init__.py') + except NotFound: + pass + else: + assert 0, 'expected exception' + + # testcase for a security issue that may exist on windows systems + import os + import ntpath + old_path = os.path + os.path = ntpath + try: + try: + f('..\\__init__.py') + except NotFound: + pass + else: + assert 0, 'expected exception' + finally: + os.path = old_path + + @emits_module_deprecation_warning + def test_endpoint_decorator(self): + from werkzeug.routing import Submount, Rule + from flask import Module + + app = flask.Flask(__name__) + app.testing = True + app.url_map.add(Submount('/foo', [ + Rule('/bar', endpoint='bar'), + Rule('/', endpoint='index') + ])) + module = Module(__name__, __name__) + + @module.endpoint('bar') + def bar(): + return 'bar' + + @module.endpoint('index') + def index(): + return 'index' + + app.register_module(module) + + c = app.test_client() + assert c.get('/foo/').data == 'index' + assert c.get('/foo/bar').data == 'bar' + + +class BlueprintTestCase(FlaskTestCase): + + def test_blueprint_specific_error_handling(self): + frontend = flask.Blueprint('frontend', __name__) + backend = flask.Blueprint('backend', __name__) + sideend = flask.Blueprint('sideend', __name__) + + @frontend.errorhandler(403) + def frontend_forbidden(e): + return 'frontend says no', 403 + + @frontend.route('/frontend-no') + def frontend_no(): + flask.abort(403) + + @backend.errorhandler(403) + def backend_forbidden(e): + return 'backend says no', 403 + + @backend.route('/backend-no') + def backend_no(): + flask.abort(403) + + @sideend.route('/what-is-a-sideend') + def sideend_no(): + flask.abort(403) + + app = flask.Flask(__name__) + app.register_blueprint(frontend) + app.register_blueprint(backend) + app.register_blueprint(sideend) + + @app.errorhandler(403) + def app_forbidden(e): + return 'application itself says no', 403 + + c = app.test_client() + + assert c.get('/frontend-no').data == 'frontend says no' + assert c.get('/backend-no').data == 'backend says no' + assert c.get('/what-is-a-sideend').data == 'application itself says no' + + def test_blueprint_url_definitions(self): + bp = flask.Blueprint('test', __name__) + + @bp.route('/foo', defaults={'baz': 42}) + def foo(bar, baz): + return '%s/%d' % (bar, baz) + + @bp.route('/bar') + def bar(bar): + return unicode(bar) + + app = flask.Flask(__name__) + app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) + app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) + + c = app.test_client() + 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') + + def test_blueprint_url_processors(self): + bp = flask.Blueprint('frontend', __name__, url_prefix='/') + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', flask.g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop('lang_code') + + @bp.route('/') + def index(): + return flask.url_for('.about') + + @bp.route('/about') + def about(): + return flask.url_for('.index') + + app = flask.Flask(__name__) + app.register_blueprint(bp) + + c = app.test_client() + + self.assert_equal(c.get('/de/').data, '/de/about') + self.assert_equal(c.get('/de/about').data, '/de/') + + def test_templates_and_static(self): + from blueprintapp import app + c = app.test_client() + + rv = c.get('/') + assert rv.data == 'Hello from the Frontend' + rv = c.get('/admin/') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/index2') + assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/static/test.txt') + assert rv.data.strip() == 'Admin File' + rv = c.get('/admin/static/css/test.css') + assert rv.data.strip() == '/* nested file */' + + with app.test_request_context(): + assert flask.url_for('admin.static', filename='test.txt') \ + == '/admin/static/test.txt' + + with app.test_request_context(): + try: + flask.render_template('missing.html') + except TemplateNotFound, e: + assert e.name == 'missing.html' + else: + assert 0, 'expected exception' + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template('nested/nested.txt') == 'I\'m nested' + + def test_templates_list(self): + from blueprintapp import app + templates = sorted(app.jinja_env.list_templates()) + self.assert_equal(templates, ['admin/index.html', + 'frontend/index.html']) + + def test_dotted_names(self): + frontend = flask.Blueprint('myapp.frontend', __name__) + backend = flask.Blueprint('myapp.backend', __name__) + + @frontend.route('/fe') + def frontend_index(): + return flask.url_for('myapp.backend.backend_index') + + @frontend.route('/fe2') + def frontend_page2(): + return flask.url_for('.frontend_index') + + @backend.route('/be') + def backend_index(): + return flask.url_for('myapp.frontend.frontend_index') + + app = flask.Flask(__name__) + app.register_blueprint(frontend) + app.register_blueprint(backend) + + c = app.test_client() + 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') + + def test_empty_url_defaults(self): + bp = flask.Blueprint('bp', __name__) + + @bp.route('/', defaults={'page': 1}) + @bp.route('/page/') + def something(page): + return str(page) + + app = flask.Flask(__name__) + app.register_blueprint(bp) + + c = app.test_client() + self.assert_equal(c.get('/').data, '1') + self.assert_equal(c.get('/page/2').data, '2') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(BlueprintTestCase)) + suite.addTest(unittest.makeSuite(ModuleTestCase)) + return suite diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py new file mode 100644 index 00000000..c8bf4687 --- /dev/null +++ b/flask/testsuite/config.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.config + ~~~~~~~~~~~~~~~~~~~~~~ + + Configuration and instances. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +# config keys used for the ConfigTestCase +TEST_KEY = 'foo' +SECRET_KEY = 'devkey' + + +class ConfigTestCase(FlaskTestCase): + + def common_object_test(self, app): + assert app.secret_key == 'devkey' + assert app.config['TEST_KEY'] == 'foo' + assert 'ConfigTestCase' not in app.config + + def test_config_from_file(self): + app = flask.Flask(__name__) + app.config.from_pyfile(__file__.rsplit('.')[0] + '.py') + self.common_object_test(app) + + def test_config_from_object(self): + app = flask.Flask(__name__) + app.config.from_object(__name__) + self.common_object_test(app) + + def test_config_from_class(self): + class Base(object): + TEST_KEY = 'foo' + class Test(Base): + SECRET_KEY = 'devkey' + app = flask.Flask(__name__) + app.config.from_object(Test) + self.common_object_test(app) + + def test_config_from_envvar(self): + env = os.environ + try: + os.environ = {} + app = flask.Flask(__name__) + try: + app.config.from_envvar('FOO_SETTINGS') + except RuntimeError, e: + assert "'FOO_SETTINGS' is not set" in str(e) + else: + assert 0, 'expected exception' + assert not app.config.from_envvar('FOO_SETTINGS', silent=True) + + os.environ = {'FOO_SETTINGS': __file__.rsplit('.')[0] + '.py'} + assert app.config.from_envvar('FOO_SETTINGS') + self.common_object_test(app) + finally: + os.environ = env + + def test_config_missing(self): + app = flask.Flask(__name__) + try: + app.config.from_pyfile('missing.cfg') + except IOError, e: + msg = str(e) + assert msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):') + assert msg.endswith("missing.cfg'") + else: + assert 0, 'expected config' + assert not app.config.from_pyfile('missing.cfg', silent=True) + + +class InstanceTestCase(FlaskTestCase): + + def test_explicit_instance_paths(self): + here = os.path.abspath(os.path.dirname(__file__)) + try: + flask.Flask(__name__, instance_path='instance') + except ValueError, e: + self.assert_('must be absolute' in str(e)) + else: + self.fail('Expected value error') + + app = flask.Flask(__name__, instance_path=here) + self.assert_equal(app.instance_path, here) + + def test_uninstalled_module_paths(self): + from config_module_app import app + here = os.path.abspath(os.path.dirname(__file__)) + self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance')) + + def test_uninstalled_package_paths(self): + from config_package_app import app + here = os.path.abspath(os.path.dirname(__file__)) + 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 + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + 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 + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + 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 + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + 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 + try: + mod.app = flask.Flask(mod.__name__) + self.assert_equal(mod.app.instance_path, + os.path.join(expected_prefix, 'var', + 'myapp-instance')) + finally: + sys.modules['myapp'] = None + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ConfigTestCase)) + suite.addTest(unittest.makeSuite(InstanceTestCase)) + return suite diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py new file mode 100644 index 00000000..d691b1dd --- /dev/null +++ b/flask/testsuite/deprecations.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.deprecations + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests deprecation support. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +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() + assert c.get('/').data == '42' + assert len(log) == 1 + assert 'init_jinja_globals' in str(log[0]['message']) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(DeprecationsTestCase)) + return suite diff --git a/flask/testsuite/examples.py b/flask/testsuite/examples.py new file mode 100644 index 00000000..2d30958f --- /dev/null +++ b/flask/testsuite/examples.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.examples + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests the examples. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import os +import unittest +from flask.testsuite import add_to_path + + +def setup_path(): + example_path = os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, 'examples') + add_to_path(os.path.join(example_path, 'flaskr')) + add_to_path(os.path.join(example_path, 'minitwit')) + + +def suite(): + setup_path() + suite = unittest.TestSuite() + try: + from minitwit_tests import MiniTwitTestCase + except ImportError: + pass + else: + suite.addTest(unittest.makeSuite(MiniTwitTestCase)) + try: + from flaskr_tests import FlaskrTestCase + except ImportError: + pass + else: + suite.addTest(unittest.makeSuite(FlaskrTestCase)) + return suite diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py new file mode 100644 index 00000000..faea9c8d --- /dev/null +++ b/flask/testsuite/helpers.py @@ -0,0 +1,295 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.helpers + ~~~~~~~~~~~~~~~~~~~~~~~ + + Various helpers. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +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_options_header + + +def has_encoding(name): + try: + import codecs + codecs.lookup(name) + return True + except LookupError: + return False + + +class JSONTestCase(FlaskTestCase): + + def test_json_bad_requests(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) + + def test_json_body_encoding(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.json + + c = app.test_client() + resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), + content_type='application/json; charset=iso-8859-15') + assert resp.data == u'Hällo Wörld'.encode('utf-8') + + def test_jsonify(self): + d = dict(a=23, b=42, c=[1, 2, 3]) + app = flask.Flask(__name__) + @app.route('/kw') + def return_kwargs(): + return flask.jsonify(**d) + @app.route('/dict') + def return_dict(): + return flask.jsonify(d) + c = app.test_client() + for url in '/kw', '/dict': + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == d + + def test_json_attr(self): + app = flask.Flask(__name__) + @app.route('/add', methods=['POST']) + def add(): + return unicode(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') + assert rv.data == '3' + + def test_template_escaping(self): + app = flask.Flask(__name__) + render = flask.render_template_string + with app.test_request_context(): + rv = render('{{ ""|tojson|safe }}') + assert rv == '"<\\/script>"' + rv = render('{{ "<\0/script>"|tojson|safe }}') + assert rv == '"<\\u0000\\/script>"' + + def test_modified_url_encoding(self): + class ModifiedRequest(flask.Request): + url_charset = 'euc-kr' + app = flask.Flask(__name__) + app.request_class = ModifiedRequest + app.url_map.charset = 'euc-kr' + + @app.route('/') + def index(): + return flask.request.args['foo'] + + rv = app.test_client().get(u'/?foo=ì •ìƒì²˜ë¦¬'.encode('euc-kr')) + assert rv.status_code == 200 + assert rv.data == u'ì •ìƒì²˜ë¦¬'.encode('utf-8') + + if not has_encoding('euc-kr'): + test_modified_url_encoding = None + + +class SendfileTestCase(FlaskTestCase): + + def test_send_file_regular(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.send_file('static/index.html') + assert rv.direct_passthrough + assert rv.mimetype == 'text/html' + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + + def test_send_file_xsendfile(self): + app = flask.Flask(__name__) + app.use_x_sendfile = True + with app.test_request_context(): + rv = flask.send_file('static/index.html') + assert rv.direct_passthrough + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + assert rv.mimetype == 'text/html' + + def test_send_file_object(self): + app = flask.Flask(__name__) + with catch_warnings() as captured: + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f) + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + assert rv.mimetype == 'text/html' + # mimetypes + etag + assert len(captured) == 2 + + app.use_x_sendfile = True + with catch_warnings() as captured: + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f) + assert rv.mimetype == 'text/html' + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + # mimetypes + etag + assert len(captured) == 2 + + app.use_x_sendfile = False + with app.test_request_context(): + with catch_warnings() as captured: + f = StringIO('Test') + rv = flask.send_file(f) + assert rv.data == 'Test' + assert rv.mimetype == 'application/octet-stream' + # etags + assert len(captured) == 1 + with catch_warnings() as captured: + f = StringIO('Test') + rv = flask.send_file(f, mimetype='text/plain') + assert rv.data == 'Test' + assert rv.mimetype == 'text/plain' + # etags + assert len(captured) == 1 + + app.use_x_sendfile = True + with catch_warnings() as captured: + with app.test_request_context(): + f = StringIO('Test') + rv = flask.send_file(f) + assert 'x-sendfile' not in rv.headers + # etags + assert len(captured) == 1 + + def test_attachment(self): + app = flask.Flask(__name__) + with catch_warnings() as captured: + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f, as_attachment=True) + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + # mimetypes + etag + assert len(captured) == 2 + + with app.test_request_context(): + assert options['filename'] == 'index.html' + rv = flask.send_file('static/index.html', as_attachment=True) + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + assert options['filename'] == 'index.html' + + with app.test_request_context(): + rv = flask.send_file(StringIO('Test'), as_attachment=True, + attachment_filename='index.txt', + add_etags=False) + assert rv.mimetype == 'text/plain' + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + assert options['filename'] == 'index.txt' + + +class LoggingTestCase(FlaskTestCase): + + def test_logger_cache(self): + app = flask.Flask(__name__) + logger1 = app.logger + assert app.logger is logger1 + assert logger1.name == __name__ + app.logger_name = __name__ + '/test_logger_cache' + assert app.logger is not logger1 + + def test_debug_log(self): + app = flask.Flask(__name__) + app.debug = True + + @app.route('/') + def index(): + app.logger.warning('the standard library is dead') + app.logger.debug('this is a debug statement') + return '' + + @app.route('/exc') + def exc(): + 1/0 + + with app.test_client() as c: + with catch_stderr() as err: + c.get('/') + out = err.getvalue() + assert 'WARNING in helpers [' in out + assert os.path.basename(__file__.rsplit('.')[0] + '.py') in out + assert 'the standard library is dead' in out + assert 'this is a debug statement' in out + + with catch_stderr() as err: + try: + c.get('/exc') + except ZeroDivisionError: + pass + else: + assert False, 'debug log ate the exception' + + def test_exception_logging(self): + out = StringIO() + app = flask.Flask(__name__) + app.logger_name = 'flask_tests/test_exception_logging' + app.logger.addHandler(StreamHandler(out)) + + @app.route('/') + def index(): + 1/0 + + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert 'Internal Server Error' in rv.data + + err = out.getvalue() + assert 'Exception on / [GET]' in err + assert 'Traceback (most recent call last):' in err + assert '1/0' in err + assert 'ZeroDivisionError:' in err + + def test_processor_exceptions(self): + app = flask.Flask(__name__) + @app.before_request + def before_request(): + if trigger == 'before': + 1/0 + @app.after_request + def after_request(response): + if trigger == 'after': + 1/0 + return response + @app.route('/') + def index(): + return 'Foo' + @app.errorhandler(500) + def internal_server_error(e): + return 'Hello Server Error', 500 + for trigger in 'before', 'after': + rv = app.test_client().get('/') + assert rv.status_code == 500 + assert rv.data == 'Hello Server Error' + + +def suite(): + suite = unittest.TestSuite() + if flask.json_available: + suite.addTest(unittest.makeSuite(JSONTestCase)) + suite.addTest(unittest.makeSuite(SendfileTestCase)) + suite.addTest(unittest.makeSuite(LoggingTestCase)) + return suite diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py new file mode 100644 index 00000000..e55807b3 --- /dev/null +++ b/flask/testsuite/signals.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.signals + ~~~~~~~~~~~~~~~~~~~~~~~ + + Signalling. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class SignalsTestCase(FlaskTestCase): + + def test_template_rendered(self): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return flask.render_template('simple_template.html', whiskey=42) + + recorded = [] + def record(sender, template, context): + recorded.append((template, context)) + + flask.template_rendered.connect(record, app) + try: + app.test_client().get('/') + assert len(recorded) == 1 + template, context = recorded[0] + assert template.name == 'simple_template.html' + assert context['whiskey'] == 42 + finally: + flask.template_rendered.disconnect(record, app) + + def test_request_signals(self): + app = flask.Flask(__name__) + calls = [] + + def before_request_signal(sender): + calls.append('before-signal') + + def after_request_signal(sender, response): + assert response.data == 'stuff' + calls.append('after-signal') + + @app.before_request + def before_request_handler(): + calls.append('before-handler') + + @app.after_request + def after_request_handler(response): + calls.append('after-handler') + response.data = 'stuff' + return response + + @app.route('/') + def index(): + calls.append('handler') + return 'ignored anyway' + + flask.request_started.connect(before_request_signal, app) + flask.request_finished.connect(after_request_signal, app) + + try: + rv = app.test_client().get('/') + assert rv.data == 'stuff' + + assert calls == ['before-signal', 'before-handler', + 'handler', 'after-handler', + 'after-signal'] + finally: + flask.request_started.disconnect(before_request_signal, app) + flask.request_finished.disconnect(after_request_signal, app) + + def test_request_exception_signal(self): + app = flask.Flask(__name__) + recorded = [] + + @app.route('/') + def index(): + 1/0 + + def record(sender, exception): + recorded.append(exception) + + flask.got_request_exception.connect(record, app) + try: + assert app.test_client().get('/').status_code == 500 + assert len(recorded) == 1 + assert isinstance(recorded[0], ZeroDivisionError) + finally: + flask.got_request_exception.disconnect(record, app) + + +def suite(): + suite = unittest.TestSuite() + if flask.signals_available: + suite.addTest(unittest.makeSuite(SignalsTestCase)) + return suite diff --git a/tests/static/index.html b/flask/testsuite/static/index.html similarity index 100% rename from tests/static/index.html rename to flask/testsuite/static/index.html diff --git a/tests/templates/_macro.html b/flask/testsuite/templates/_macro.html similarity index 100% rename from tests/templates/_macro.html rename to flask/testsuite/templates/_macro.html diff --git a/tests/templates/context_template.html b/flask/testsuite/templates/context_template.html similarity index 100% rename from tests/templates/context_template.html rename to flask/testsuite/templates/context_template.html diff --git a/tests/templates/escaping_template.html b/flask/testsuite/templates/escaping_template.html similarity index 100% rename from tests/templates/escaping_template.html rename to flask/testsuite/templates/escaping_template.html diff --git a/tests/templates/mail.txt b/flask/testsuite/templates/mail.txt similarity index 100% rename from tests/templates/mail.txt rename to flask/testsuite/templates/mail.txt diff --git a/tests/templates/nested/nested.txt b/flask/testsuite/templates/nested/nested.txt similarity index 100% rename from tests/templates/nested/nested.txt rename to flask/testsuite/templates/nested/nested.txt diff --git a/tests/templates/simple_template.html b/flask/testsuite/templates/simple_template.html similarity index 100% rename from tests/templates/simple_template.html rename to flask/testsuite/templates/simple_template.html diff --git a/tests/templates/template_filter.html b/flask/testsuite/templates/template_filter.html similarity index 100% rename from tests/templates/template_filter.html rename to flask/testsuite/templates/template_filter.html diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py new file mode 100644 index 00000000..e980ff92 --- /dev/null +++ b/flask/testsuite/templating.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.templating + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Template functionality + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class TemplatingTestCase(FlaskTestCase): + + def test_context_processing(self): + app = flask.Flask(__name__) + @app.context_processor + def context_processor(): + return {'injected_value': 42} + @app.route('/') + def index(): + return flask.render_template('context_template.html', value=23) + rv = app.test_client().get('/') + assert rv.data == '

23|42' + + def test_original_win(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + return flask.render_template_string('{{ config }}', config=42) + rv = app.test_client().get('/') + assert rv.data == '42' + + def test_standard_context(self): + app = flask.Flask(__name__) + app.secret_key = 'development key' + @app.route('/') + def index(): + flask.g.foo = 23 + flask.session['test'] = 'aha' + return flask.render_template_string(''' + {{ request.args.foo }} + {{ g.foo }} + {{ config.DEBUG }} + {{ session.test }} + ''') + rv = app.test_client().get('/?foo=42') + assert rv.data.split() == ['42', '23', 'False', 'aha'] + + def test_escaping(self): + text = '

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

Hello World!', + '

Hello World!', + '

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

Hello World!' + ] + + def test_no_escaping(self): + app = flask.Flask(__name__) + with app.test_request_context(): + assert flask.render_template_string('{{ foo }}', + foo='') == '' + assert flask.render_template('mail.txt', foo='') \ + == ' Mail' + + def test_macros(self): + app = flask.Flask(__name__) + with app.test_request_context(): + macro = flask.get_template_attribute('_macro.html', 'hello') + assert macro('World') == 'Hello World!' + + def test_template_filter(self): + app = flask.Flask(__name__) + @app.template_filter() + def my_reverse(s): + return s[::-1] + assert 'my_reverse' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['my_reverse'] == my_reverse + assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' + + def test_template_filter_with_name(self): + app = flask.Flask(__name__) + @app.template_filter('strrev') + def my_reverse(s): + return s[::-1] + assert 'strrev' in app.jinja_env.filters.keys() + assert app.jinja_env.filters['strrev'] == my_reverse + assert app.jinja_env.filters['strrev']('abcd') == 'dcba' + + def test_template_filter_with_template(self): + app = flask.Flask(__name__) + @app.template_filter() + def super_reverse(s): + return s[::-1] + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + assert rv.data == 'dcba' + + def test_template_filter_with_name_and_template(self): + app = flask.Flask(__name__) + @app.template_filter('super_reverse') + def my_reverse(s): + return s[::-1] + @app.route('/') + def index(): + return flask.render_template('template_filter.html', value='abcd') + rv = app.test_client().get('/') + assert rv.data == 'dcba' + + def test_custom_template_loader(self): + class MyFlask(flask.Flask): + def create_global_jinja_loader(self): + from jinja2 import DictLoader + return DictLoader({'index.html': 'Hello Custom World!'}) + app = MyFlask(__name__) + @app.route('/') + def index(): + return flask.render_template('index.html') + c = app.test_client() + rv = c.get('/') + assert rv.data == 'Hello Custom World!' + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TemplatingTestCase)) + return suite diff --git a/tests/blueprintapp/__init__.py b/flask/testsuite/test_apps/blueprintapp/__init__.py similarity index 100% rename from tests/blueprintapp/__init__.py rename to flask/testsuite/test_apps/blueprintapp/__init__.py diff --git a/tests/blueprintapp/apps/__init__.py b/flask/testsuite/test_apps/blueprintapp/apps/__init__.py similarity index 100% rename from tests/blueprintapp/apps/__init__.py rename to flask/testsuite/test_apps/blueprintapp/apps/__init__.py diff --git a/tests/blueprintapp/apps/admin/__init__.py b/flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py similarity index 100% rename from tests/blueprintapp/apps/admin/__init__.py rename to flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py diff --git a/tests/blueprintapp/apps/admin/static/css/test.css b/flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css similarity index 100% rename from tests/blueprintapp/apps/admin/static/css/test.css rename to flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css diff --git a/tests/blueprintapp/apps/admin/static/test.txt b/flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt similarity index 100% rename from tests/blueprintapp/apps/admin/static/test.txt rename to flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt diff --git a/tests/blueprintapp/apps/admin/templates/admin/index.html b/flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html similarity index 100% rename from tests/blueprintapp/apps/admin/templates/admin/index.html rename to flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html diff --git a/tests/blueprintapp/apps/frontend/__init__.py b/flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py similarity index 100% rename from tests/blueprintapp/apps/frontend/__init__.py rename to flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py diff --git a/tests/blueprintapp/apps/frontend/templates/frontend/index.html b/flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html similarity index 100% rename from tests/blueprintapp/apps/frontend/templates/frontend/index.html rename to flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html diff --git a/flask/testsuite/test_apps/config_module_app.py b/flask/testsuite/test_apps/config_module_app.py new file mode 100644 index 00000000..380d46bf --- /dev/null +++ b/flask/testsuite/test_apps/config_module_app.py @@ -0,0 +1,4 @@ +import os +import flask +here = os.path.abspath(os.path.dirname(__file__)) +app = flask.Flask(__name__) diff --git a/flask/testsuite/test_apps/config_package_app/__init__.py b/flask/testsuite/test_apps/config_package_app/__init__.py new file mode 100644 index 00000000..380d46bf --- /dev/null +++ b/flask/testsuite/test_apps/config_package_app/__init__.py @@ -0,0 +1,4 @@ +import os +import flask +here = os.path.abspath(os.path.dirname(__file__)) +app = flask.Flask(__name__) diff --git a/tests/moduleapp/__init__.py b/flask/testsuite/test_apps/moduleapp/__init__.py similarity index 100% rename from tests/moduleapp/__init__.py rename to flask/testsuite/test_apps/moduleapp/__init__.py diff --git a/tests/moduleapp/apps/__init__.py b/flask/testsuite/test_apps/moduleapp/apps/__init__.py similarity index 100% rename from tests/moduleapp/apps/__init__.py rename to flask/testsuite/test_apps/moduleapp/apps/__init__.py diff --git a/tests/moduleapp/apps/admin/__init__.py b/flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py similarity index 100% rename from tests/moduleapp/apps/admin/__init__.py rename to flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py diff --git a/tests/moduleapp/apps/admin/static/css/test.css b/flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css similarity index 100% rename from tests/moduleapp/apps/admin/static/css/test.css rename to flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css diff --git a/tests/moduleapp/apps/admin/static/test.txt b/flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt similarity index 100% rename from tests/moduleapp/apps/admin/static/test.txt rename to flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt diff --git a/tests/moduleapp/apps/admin/templates/index.html b/flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html similarity index 100% rename from tests/moduleapp/apps/admin/templates/index.html rename to flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html diff --git a/tests/moduleapp/apps/frontend/__init__.py b/flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py similarity index 100% rename from tests/moduleapp/apps/frontend/__init__.py rename to flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py diff --git a/tests/moduleapp/apps/frontend/templates/index.html b/flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html similarity index 100% rename from tests/moduleapp/apps/frontend/templates/index.html rename to flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html diff --git a/tests/subdomaintestmodule/__init__.py b/flask/testsuite/test_apps/subdomaintestmodule/__init__.py similarity index 100% rename from tests/subdomaintestmodule/__init__.py rename to flask/testsuite/test_apps/subdomaintestmodule/__init__.py diff --git a/tests/subdomaintestmodule/static/hello.txt b/flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt similarity index 100% rename from tests/subdomaintestmodule/static/hello.txt rename to flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py new file mode 100644 index 00000000..4e16c257 --- /dev/null +++ b/flask/testsuite/testing.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.testing + ~~~~~~~~~~~~~~~~~~~~~~~ + + Test client and more. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from flask.testsuite import FlaskTestCase + + +class TestToolsTestCase(FlaskTestCase): + + def test_environ_defaults_from_config(self): + app = flask.Flask(__name__) + app.testing = True + app.config['SERVER_NAME'] = 'example.com:1234' + app.config['APPLICATION_ROOT'] = '/foo' + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + 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/') + + def test_environ_defaults(self): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + return flask.request.url + + ctx = app.test_request_context() + self.assert_equal(ctx.request.url, 'http://localhost/') + with app.test_client() as c: + rv = c.get('/') + self.assert_equal(rv.data, 'http://localhost/') + + def test_session_transactions(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + @app.route('/') + def index(): + return unicode(flask.session['foo']) + + with app.test_client() as c: + with c.session_transaction() as sess: + self.assert_equal(len(sess), 0) + sess['foo'] = [42] + self.assert_equal(len(sess), 1) + rv = c.get('/') + self.assert_equal(rv.data, '[42]') + + def test_session_transactions_no_null_sessions(self): + app = flask.Flask(__name__) + app.testing = True + + with app.test_client() as c: + try: + with c.session_transaction() as sess: + pass + except RuntimeError, e: + self.assert_('Session backend did not open a session' in str(e)) + else: + self.fail('Expected runtime error') + + def test_session_transactions_keep_context(self): + app = flask.Flask(__name__) + app.testing = True + app.secret_key = 'testing' + + with app.test_client() as c: + rv = c.get('/') + req = flask.request._get_current_object() + with c.session_transaction(): + self.assert_(req is flask.request._get_current_object()) + + def test_test_client_context_binding(self): + app = flask.Flask(__name__) + @app.route('/') + def index(): + flask.g.value = 42 + return 'Hello World!' + + @app.route('/other') + def other(): + 1/0 + + with app.test_client() as c: + resp = c.get('/') + assert flask.g.value == 42 + assert resp.data == 'Hello World!' + assert resp.status_code == 200 + + resp = c.get('/other') + assert not hasattr(flask.g, 'value') + assert 'Internal Server Error' in resp.data + assert resp.status_code == 500 + flask.g.value = 23 + + try: + flask.g.value + except (AttributeError, RuntimeError): + pass + else: + raise AssertionError('some kind of exception expected') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestToolsTestCase)) + return suite diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py new file mode 100644 index 00000000..a89c44ac --- /dev/null +++ b/flask/testsuite/views.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.views + ~~~~~~~~~~~~~~~~~~~~~ + + Pluggable views. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +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): + c = app.test_client() + + self.assert_equal(c.get('/').data, 'GET') + self.assert_equal(c.post('/').data, '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']) + + def test_basic_view(self): + app = flask.Flask(__name__) + + 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')) + self.common_test(app) + + def test_method_based_view(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + return 'GET' + def post(self): + return 'POST' + + app.add_url_rule('/', view_func=Index.as_view('index')) + + self.common_test(app) + + def test_view_patching(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + 1/0 + def post(self): + 1/0 + + class Other(Index): + def get(self): + return 'GET' + def post(self): + return 'POST' + + view = Index.as_view('index') + view.view_class = Other + app.add_url_rule('/', view_func=view) + self.common_test(app) + + def test_view_inheritance(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + return 'GET' + def post(self): + return 'POST' + + class BetterIndex(Index): + def delete(self): + return 'DELETE' + + app.add_url_rule('/', view_func=BetterIndex.as_view('index')) + c = app.test_client() + + meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) + self.assert_equal(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) + + def test_view_decorators(self): + app = flask.Flask(__name__) + + def add_x_parachute(f): + def new_function(*args, **kwargs): + resp = flask.make_response(f(*args, **kwargs)) + resp.headers['X-Parachute'] = 'awesome' + return resp + return new_function + + class Index(flask.views.View): + decorators = [add_x_parachute] + def dispatch_request(self): + return 'Awesome' + + app.add_url_rule('/', view_func=Index.as_view('index')) + c = app.test_client() + rv = c.get('/') + self.assert_equal(rv.headers['X-Parachute'], 'awesome') + self.assert_equal(rv.data, 'Awesome') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ViewTestCase)) + return suite diff --git a/tests/flaskext_test.py b/scripts/flaskext_test.py similarity index 100% rename from tests/flaskext_test.py rename to scripts/flaskext_test.py diff --git a/setup.py b/setup.py index 1809e05e..a51c3887 100644 --- a/setup.py +++ b/setup.py @@ -77,12 +77,6 @@ class run_audit(Command): else: print ("No problems found in sourcecode.") -def run_tests(): - import os, sys - sys.path.append(os.path.join(os.path.dirname(__file__), 'tests')) - from flask_tests import suite - return suite() - setup( name='Flask', @@ -112,5 +106,5 @@ setup( 'Topic :: Software Development :: Libraries :: Python Modules' ], cmdclass={'audit': run_audit}, - test_suite='__main__.run_tests' + test_suite='flask.testsuite.suite' ) diff --git a/tests/flask_tests.py b/tests/flask_tests.py deleted file mode 100644 index dcc3e1a0..00000000 --- a/tests/flask_tests.py +++ /dev/null @@ -1,2329 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Flask Tests - ~~~~~~~~~~~ - - Tests Flask itself. The majority of Flask is already tested - as part of Werkzeug. - - :copyright: (c) 2010 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -from __future__ import with_statement -import os -import re -import sys -import flask -import flask.views -import unittest -import warnings -from threading import Thread -from logging import StreamHandler -from contextlib import contextmanager -from functools import update_wrapper -from datetime import datetime -from werkzeug import parse_date, parse_options_header -from werkzeug.exceptions import NotFound, BadRequest -from werkzeug.http import parse_set_header -from jinja2 import TemplateNotFound -from cStringIO import StringIO - -example_path = os.path.join(os.path.dirname(__file__), '..', 'examples') -sys.path.append(os.path.join(example_path, 'flaskr')) -sys.path.append(os.path.join(example_path, 'minitwit')) - - -def has_encoding(name): - try: - import codecs - codecs.lookup(name) - return True - except LookupError: - return False - - -# config keys used for the ConfigTestCase -TEST_KEY = 'foo' -SECRET_KEY = 'devkey' - - -# import moduleapp here because it uses deprecated features and we don't -# want to see the warnings -warnings.simplefilter('ignore', DeprecationWarning) -from moduleapp import app as moduleapp -warnings.simplefilter('default', DeprecationWarning) - - -@contextmanager -def catch_warnings(): - """Catch warnings in a with block in a list""" - # make sure deprecation warnings are active in tests - warnings.simplefilter('default', category=DeprecationWarning) - - filters = warnings.filters - warnings.filters = filters[:] - old_showwarning = warnings.showwarning - log = [] - def showwarning(message, category, filename, lineno, file=None, line=None): - log.append(locals()) - try: - warnings.showwarning = showwarning - yield log - finally: - warnings.filters = filters - warnings.showwarning = old_showwarning - - -@contextmanager -def catch_stderr(): - """Catch stderr in a StringIO""" - old_stderr = sys.stderr - sys.stderr = rv = StringIO() - try: - yield rv - finally: - sys.stderr = old_stderr - - -def emits_module_deprecation_warning(f): - def new_f(*args, **kwargs): - with catch_warnings() as log: - f(*args, **kwargs) - assert log, 'expected deprecation warning' - for entry in log: - assert 'Modules are deprecated' in str(entry['message']) - return update_wrapper(new_f, f) - - -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.assertEqual(flask._request_ctx_stack.top, None) - - def tearDown(self): - unittest.TestCase.tearDown(self) - self.ensure_clean_request_context() - - -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'): - assert index() == 'Hello World!' - with app.test_request_context('/meh'): - assert meh() == 'http://localhost/meh' - assert flask._request_ctx_stack.top is None - - def test_context_test(self): - app = flask.Flask(__name__) - assert not flask.request - assert not flask.has_request_context() - ctx = app.test_request_context() - ctx.push() - try: - assert flask.request - 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() - assert index() == 'Hello World!' - ctx.pop() - try: - index() - except RuntimeError: - pass - else: - assert 0, 'expected runtime error' - - -class BasicFunctionalityTestCase(FlaskTestCase): - - def test_options_work(self): - app = flask.Flask(__name__) - @app.route('/', methods=['GET', 'POST']) - def index(): - return 'Hello World' - rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - assert rv.data == '' - - def test_options_on_multiple_rules(self): - app = flask.Flask(__name__) - @app.route('/', methods=['GET', 'POST']) - def index(): - return 'Hello World' - @app.route('/', methods=['PUT']) - def index_put(): - return 'Aha!' - rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - def test_options_handling_disabled(self): - app = flask.Flask(__name__) - def index(): - return 'Hello World!' - index.provide_automatic_options = False - app.route('/')(index) - rv = app.test_client().open('/', method='OPTIONS') - assert rv.status_code == 405 - - app = flask.Flask(__name__) - def index2(): - return 'Hello World!' - index2.provide_automatic_options = True - app.route('/', methods=['OPTIONS'])(index2) - rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['OPTIONS'] - - def test_request_dispatching(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return flask.request.method - @app.route('/more', methods=['GET', 'POST']) - def more(): - return flask.request.method - - c = app.test_client() - assert c.get('/').data == 'GET' - rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] - rv = c.head('/') - assert rv.status_code == 200 - assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' - rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - - def test_url_mapping(self): - app = flask.Flask(__name__) - def index(): - return flask.request.method - def more(): - return flask.request.method - - app.add_url_rule('/', 'index', index) - app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) - - c = app.test_client() - assert c.get('/').data == 'GET' - rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] - rv = c.head('/') - assert rv.status_code == 200 - assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' - rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - - def test_werkzeug_routing(self): - from werkzeug.routing import Submount, Rule - app = flask.Flask(__name__) - app.url_map.add(Submount('/foo', [ - Rule('/bar', endpoint='bar'), - Rule('/', endpoint='index') - ])) - def bar(): - return 'bar' - def index(): - return 'index' - app.view_functions['bar'] = bar - app.view_functions['index'] = index - - c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' - - def test_endpoint_decorator(self): - from werkzeug.routing import Submount, Rule - app = flask.Flask(__name__) - app.url_map.add(Submount('/foo', [ - Rule('/bar', endpoint='bar'), - Rule('/', endpoint='index') - ])) - - @app.endpoint('bar') - def bar(): - return 'bar' - - @app.endpoint('index') - def index(): - return 'index' - - c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' - - def test_session(self): - app = flask.Flask(__name__) - app.secret_key = 'testkey' - @app.route('/set', methods=['POST']) - def set(): - flask.session['value'] = flask.request.form['value'] - return 'value set' - @app.route('/get') - def get(): - return flask.session['value'] - - c = app.test_client() - assert c.post('/set', data={'value': '42'}).data == 'value set' - assert c.get('/get').data == '42' - - def test_session_using_server_name(self): - app = flask.Flask(__name__) - app.config.update( - SECRET_KEY='foo', - SERVER_NAME='example.com' - ) - @app.route('/') - def index(): - flask.session['testing'] = 42 - return 'Hello World' - rv = app.test_client().get('/', 'http://example.com/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() - - def test_session_using_server_name_and_port(self): - app = flask.Flask(__name__) - app.config.update( - SECRET_KEY='foo', - SERVER_NAME='example.com:8080' - ) - @app.route('/') - def index(): - flask.session['testing'] = 42 - return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() - - def test_session_using_application_root(self): - class PrefixPathMiddleware(object): - def __init__(self, app, prefix): - self.app = app - self.prefix = prefix - def __call__(self, environ, start_response): - environ['SCRIPT_NAME'] = self.prefix - return self.app(environ, start_response) - - app = flask.Flask(__name__) - app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar') - app.config.update( - SECRET_KEY='foo', - APPLICATION_ROOT='/bar' - ) - @app.route('/') - def index(): - flask.session['testing'] = 42 - return 'Hello World' - rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'path=/bar' in rv.headers['set-cookie'].lower() - - def test_missing_session(self): - app = flask.Flask(__name__) - def expect_exception(f, *args, **kwargs): - try: - f(*args, **kwargs) - except RuntimeError, e: - assert e.args and 'session is unavailable' in e.args[0] - else: - assert False, 'expected exception' - with app.test_request_context(): - assert flask.session.get('missing_key') is None - expect_exception(flask.session.__setitem__, 'foo', 42) - expect_exception(flask.session.pop, 'foo') - - def test_session_expiration(self): - permanent = True - app = flask.Flask(__name__) - app.secret_key = 'testkey' - @app.route('/') - def index(): - flask.session['test'] = 42 - flask.session.permanent = permanent - return '' - - @app.route('/test') - def test(): - return unicode(flask.session.permanent) - - client = app.test_client() - rv = client.get('/') - assert '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 - assert expires.year == expected.year - assert expires.month == expected.month - assert expires.day == expected.day - - rv = client.get('/test') - assert rv.data == 'True' - - permanent = False - rv = app.test_client().get('/') - assert 'set-cookie' in rv.headers - match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) - assert match is None - - def test_flashes(self): - app = flask.Flask(__name__) - app.secret_key = 'testkey' - - with app.test_request_context(): - assert not flask.session.modified - flask.flash('Zap') - flask.session.modified = False - flask.flash('Zip') - assert flask.session.modified - assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] - - def test_extended_flashing(self): - app = flask.Flask(__name__) - app.secret_key = 'testkey' - - @app.route('/') - def index(): - flask.flash(u'Hello World') - flask.flash(u'Hello World', 'error') - flask.flash(flask.Markup(u'Testing'), 'warning') - return '' - - @app.route('/test') - def test(): - messages = flask.get_flashed_messages(with_categories=True) - assert len(messages) == 3 - assert messages[0] == ('message', u'Hello World') - assert messages[1] == ('error', u'Hello World') - assert messages[2] == ('warning', flask.Markup(u'Testing')) - return '' - messages = flask.get_flashed_messages() - assert len(messages) == 3 - assert messages[0] == u'Hello World' - assert messages[1] == u'Hello World' - assert messages[2] == flask.Markup(u'Testing') - - c = app.test_client() - c.get('/') - c.get('/test') - - def test_request_processing(self): - app = flask.Flask(__name__) - evts = [] - @app.before_request - def before_request(): - evts.append('before') - @app.after_request - def after_request(response): - response.data += '|after' - evts.append('after') - return response - @app.route('/') - def index(): - assert 'before' in evts - assert 'after' not in evts - return 'request' - assert 'after' not in evts - rv = app.test_client().get('/').data - assert 'after' in evts - assert rv == 'request|after' - - def test_teardown_request_handler(self): - called = [] - app = flask.Flask(__name__) - @app.teardown_request - def teardown_request(exc): - called.append(True) - return "Ignored" - @app.route('/') - def root(): - return "Response" - rv = app.test_client().get('/') - assert rv.status_code == 200 - assert 'Response' in rv.data - assert len(called) == 1 - - def test_teardown_request_handler_debug_mode(self): - called = [] - app = flask.Flask(__name__) - app.testing = True - @app.teardown_request - def teardown_request(exc): - called.append(True) - return "Ignored" - @app.route('/') - def root(): - return "Response" - rv = app.test_client().get('/') - assert rv.status_code == 200 - assert 'Response' in rv.data - assert len(called) == 1 - - def test_teardown_request_handler_error(self): - called = [] - app = flask.Flask(__name__) - @app.teardown_request - def teardown_request1(exc): - assert type(exc) == ZeroDivisionError - called.append(True) - # This raises a new error and blows away sys.exc_info(), so we can - # test that all teardown_requests get passed the same original - # exception. - try: - raise TypeError - except: - pass - @app.teardown_request - def teardown_request2(exc): - assert type(exc) == ZeroDivisionError - called.append(True) - # This raises a new error and blows away sys.exc_info(), so we can - # test that all teardown_requests get passed the same original - # exception. - try: - raise TypeError - except: - pass - @app.route('/') - def fails(): - 1/0 - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert 'Internal Server Error' in rv.data - assert len(called) == 2 - - def test_before_after_request_order(self): - called = [] - app = flask.Flask(__name__) - @app.before_request - def before1(): - called.append(1) - @app.before_request - def before2(): - called.append(2) - @app.after_request - def after1(response): - called.append(4) - return response - @app.after_request - def after2(response): - called.append(3) - return response - @app.teardown_request - def finish1(exc): - called.append(6) - @app.teardown_request - def finish2(exc): - called.append(5) - @app.route('/') - def index(): - return '42' - rv = app.test_client().get('/') - assert rv.data == '42' - assert called == [1, 2, 3, 4, 5, 6] - - def test_error_handling(self): - app = flask.Flask(__name__) - @app.errorhandler(404) - def not_found(e): - return 'not found', 404 - @app.errorhandler(500) - def internal_server_error(e): - return 'internal server error', 500 - @app.route('/') - def index(): - flask.abort(404) - @app.route('/error') - def error(): - 1 // 0 - c = app.test_client() - rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' - rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data - - def test_before_request_and_routing_errors(self): - app = flask.Flask(__name__) - @app.before_request - def attach_something(): - flask.g.something = 'value' - @app.errorhandler(404) - def return_something(error): - return flask.g.something, 404 - rv = app.test_client().get('/') - assert rv.status_code == 404 - assert rv.data == 'value' - - def test_user_error_handling(self): - class MyException(Exception): - pass - - app = flask.Flask(__name__) - @app.errorhandler(MyException) - def handle_my_exception(e): - assert isinstance(e, MyException) - return '42' - @app.route('/') - def index(): - raise MyException() - - c = app.test_client() - assert c.get('/').data == '42' - - def test_trapping_of_bad_request_key_errors(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/fail') - def fail(): - flask.request.form['missing_key'] - c = app.test_client() - assert c.get('/fail').status_code == 400 - - app.config['TRAP_BAD_REQUEST_ERRORS'] = True - c = app.test_client() - try: - c.get('/fail') - except KeyError, e: - assert isinstance(e, BadRequest) - else: - self.fail('Expected exception') - - def test_trapping_of_all_http_exceptions(self): - app = flask.Flask(__name__) - app.testing = True - app.config['TRAP_HTTP_EXCEPTIONS'] = True - @app.route('/fail') - def fail(): - flask.abort(404) - - c = app.test_client() - try: - c.get('/fail') - except NotFound, e: - pass - else: - self.fail('Expected exception') - - def test_enctype_debug_helper(self): - from flask.debughelpers import DebugFilesKeyError - app = flask.Flask(__name__) - app.debug = True - @app.route('/fail', methods=['POST']) - def index(): - return flask.request.files['foo'].filename - - # with statement is important because we leave an exception on the - # stack otherwise and we want to ensure that this is not the case - # to not negatively affect other tests. - with app.test_client() as c: - try: - c.post('/fail', data={'foo': 'index.txt'}) - except DebugFilesKeyError, e: - assert 'no file contents were transmitted' in str(e) - assert 'This was submitted: "index.txt"' in str(e) - 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() - assert buffer == [] - ctx.pop() - assert buffer == [None] - - def test_response_creation(self): - app = flask.Flask(__name__) - @app.route('/unicode') - def from_unicode(): - return u'Hällo Wörld' - @app.route('/string') - def from_string(): - return u'Hällo Wörld'.encode('utf-8') - @app.route('/args') - def from_tuple(): - return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' - c = app.test_client() - assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') - assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') - rv = c.get('/args') - assert rv.data == 'Meh' - assert rv.headers['X-Foo'] == 'Testing' - assert rv.status_code == 400 - assert rv.mimetype == 'text/plain' - - def test_make_response(self): - app = flask.Flask(__name__) - with app.test_request_context(): - rv = flask.make_response() - assert rv.status_code == 200 - assert rv.data == '' - assert rv.mimetype == 'text/html' - - rv = flask.make_response('Awesome') - assert rv.status_code == 200 - assert rv.data == 'Awesome' - assert rv.mimetype == 'text/html' - - rv = flask.make_response('W00t', 404) - assert rv.status_code == 404 - assert rv.data == 'W00t' - assert rv.mimetype == 'text/html' - - def test_url_generation(self): - app = flask.Flask(__name__) - @app.route('/hello/', methods=['POST']) - def hello(): - pass - with app.test_request_context(): - assert flask.url_for('hello', name='test x') == '/hello/test%20x' - assert flask.url_for('hello', name='test x', _external=True) \ - == 'http://localhost/hello/test%20x' - - def test_custom_converters(self): - from werkzeug.routing import BaseConverter - class ListConverter(BaseConverter): - def to_python(self, value): - return value.split(',') - def to_url(self, value): - base_to_url = super(ListConverter, self).to_url - return ','.join(base_to_url(x) for x in value) - app = flask.Flask(__name__) - app.url_map.converters['list'] = ListConverter - @app.route('/') - def index(args): - return '|'.join(args) - c = app.test_client() - assert c.get('/1,2,3').data == '1|2|3' - - def test_static_files(self): - app = flask.Flask(__name__) - rv = app.test_client().get('/static/index.html') - assert rv.status_code == 200 - assert rv.data.strip() == '

Hello World!

' - with app.test_request_context(): - assert flask.url_for('static', filename='index.html') \ - == '/static/index.html' - - def test_none_response(self): - app = flask.Flask(__name__) - @app.route('/') - def test(): - return None - try: - app.test_client().get('/') - except ValueError, e: - assert str(e) == 'View function did not return a response' - pass - else: - assert "Expected ValueError" - - def test_request_locals(self): - self.assertEqual(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('/'): - assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' - - with app.test_request_context('/'): - assert 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: - assert isinstance(e, ValueError) - assert str(e) == "the server name provided " + \ - "('localhost.localdomain:5000') does not match the " + \ - "server name from the WSGI environment ('localhost')", str(e) - - 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( - SERVER_NAME='localhost.localdomain:5000' - ) - @app.route('/') - def index(): - return 'Foo' - - @app.route('/', subdomain='foo') - def subdomain(): - return 'Foo SubDomain' - - try: - rv = app.test_client().get('/') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - rv = app.test_client().get('/', 'http://localhost.localdomain:5000') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - rv = app.test_client().get('/', 'https://localhost.localdomain:5000') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - app.config.update(SERVER_NAME='localhost.localdomain') - rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - try: - app.config.update(SERVER_NAME='localhost.localdomain:443') - rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' - except ValueError, e: - assert str(e) == "the server name provided " + \ - "('localhost.localdomain:443') does not match the " + \ - "server name from the WSGI environment ('localhost.localdomain')", str(e) - - try: - app.config.update(SERVER_NAME='localhost.localdomain') - app.test_client().get('/', 'http://foo.localhost') - except ValueError, e: - assert str(e) == "the server name provided " + \ - "('localhost.localdomain') does not match the " + \ - "server name from the WSGI environment ('foo.localhost')", str(e) - - try: - rv = app.test_client().get('/', 'http://foo.localhost.localdomain') - assert rv.data == 'Foo SubDomain' - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) - - def test_exception_propagation(self): - def apprunner(configkey): - app = flask.Flask(__name__) - @app.route('/') - def index(): - 1/0 - c = app.test_client() - if config_key is not None: - app.config[config_key] = True - try: - resp = c.get('/') - except Exception: - pass - else: - self.fail('expected exception') - else: - assert c.get('/').status_code == 500 - - # we have to run this test in an isolated thread because if the - # debug flag is set to true and an exception happens the context is - # not torn down. This causes other tests that run after this fail - # when they expect no exception on the stack. - for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None: - t = Thread(target=apprunner, args=(config_key,)) - t.start() - t.join() - - def test_max_content_length(self): - app = flask.Flask(__name__) - app.config['MAX_CONTENT_LENGTH'] = 64 - @app.before_request - def always_first(): - flask.request.form['myfile'] - assert False - @app.route('/accept', methods=['POST']) - def accept_file(): - flask.request.form['myfile'] - assert False - @app.errorhandler(413) - def catcher(error): - return '42' - - c = app.test_client() - rv = c.post('/accept', data={'myfile': 'foo' * 100}) - assert rv.data == '42' - - def test_url_processors(self): - app = flask.Flask(__name__) - - @app.url_defaults - def add_language_code(endpoint, values): - if flask.g.lang_code is not None and \ - app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): - values.setdefault('lang_code', flask.g.lang_code) - - @app.url_value_preprocessor - def pull_lang_code(endpoint, values): - flask.g.lang_code = values.pop('lang_code', None) - - @app.route('//') - def index(): - return flask.url_for('about') - - @app.route('//about') - def about(): - return flask.url_for('something_else') - - @app.route('/foo') - def something_else(): - return flask.url_for('about', lang_code='en') - - c = app.test_client() - - self.assertEqual(c.get('/de/').data, '/de/about') - self.assertEqual(c.get('/de/about').data, '/foo') - self.assertEqual(c.get('/foo').data, '/en/about') - - def test_debug_mode_complains_after_first_request(self): - app = flask.Flask(__name__) - app.debug = True - @app.route('/') - def index(): - return 'Awesome' - self.assert_(not app.got_first_request) - self.assertEqual(app.test_client().get('/').data, 'Awesome') - try: - @app.route('/foo') - def broken(): - return 'Meh' - except AssertionError, e: - self.assert_('A setup function was called' in str(e)) - else: - self.fail('Expected exception') - - app.debug = False - @app.route('/foo') - def working(): - return 'Meh' - self.assertEqual(app.test_client().get('/foo').data, 'Meh') - self.assert_(app.got_first_request) - - def test_before_first_request_functions(self): - got = [] - app = flask.Flask(__name__) - @app.before_first_request - def foo(): - got.append(42) - c = app.test_client() - c.get('/') - self.assertEqual(got, [42]) - c.get('/') - self.assertEqual(got, [42]) - self.assert_(app.got_first_request) - - def test_routing_redirect_debugging(self): - app = flask.Flask(__name__) - app.debug = True - @app.route('/foo/', methods=['GET', 'POST']) - def foo(): - return 'success' - with app.test_client() as c: - try: - c.post('/foo', data={}) - except AssertionError, 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)) - else: - self.fail('Expected exception') - - rv = c.get('/foo', data={}, follow_redirects=True) - self.assertEqual(rv.data, 'success') - - app.debug = False - with app.test_client() as c: - rv = c.post('/foo', data={}, follow_redirects=True) - self.assertEqual(rv.data, 'success') - - -class TestToolsTestCase(FlaskTestCase): - - def test_environ_defaults_from_config(self): - app = flask.Flask(__name__) - app.testing = True - app.config['SERVER_NAME'] = 'example.com:1234' - app.config['APPLICATION_ROOT'] = '/foo' - @app.route('/') - def index(): - return flask.request.url - - ctx = app.test_request_context() - self.assertEqual(ctx.request.url, 'http://example.com:1234/foo/') - with app.test_client() as c: - rv = c.get('/') - self.assertEqual(rv.data, 'http://example.com:1234/foo/') - - def test_environ_defaults(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/') - def index(): - return flask.request.url - - ctx = app.test_request_context() - self.assertEqual(ctx.request.url, 'http://localhost/') - with app.test_client() as c: - rv = c.get('/') - self.assertEqual(rv.data, 'http://localhost/') - - def test_session_transactions(self): - app = flask.Flask(__name__) - app.testing = True - app.secret_key = 'testing' - - @app.route('/') - def index(): - return unicode(flask.session['foo']) - - with app.test_client() as c: - with c.session_transaction() as sess: - self.assertEqual(len(sess), 0) - sess['foo'] = [42] - self.assertEqual(len(sess), 1) - rv = c.get('/') - self.assertEqual(rv.data, '[42]') - - def test_session_transactions_no_null_sessions(self): - app = flask.Flask(__name__) - app.testing = True - - with app.test_client() as c: - try: - with c.session_transaction() as sess: - pass - except RuntimeError, e: - self.assert_('Session backend did not open a session' in str(e)) - else: - self.fail('Expected runtime error') - - def test_session_transactions_keep_context(self): - app = flask.Flask(__name__) - app.testing = True - app.secret_key = 'testing' - - with app.test_client() as c: - rv = c.get('/') - req = flask.request._get_current_object() - with c.session_transaction(): - self.assert_(req is flask.request._get_current_object()) - - def test_test_client_context_binding(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - flask.g.value = 42 - return 'Hello World!' - - @app.route('/other') - def other(): - 1/0 - - with app.test_client() as c: - resp = c.get('/') - assert flask.g.value == 42 - assert resp.data == 'Hello World!' - assert resp.status_code == 200 - - resp = c.get('/other') - assert not hasattr(flask.g, 'value') - assert 'Internal Server Error' in resp.data - assert resp.status_code == 500 - flask.g.value = 23 - - try: - flask.g.value - except (AttributeError, RuntimeError): - pass - else: - raise AssertionError('some kind of exception expected') - - -class InstanceTestCase(FlaskTestCase): - - def test_explicit_instance_paths(self): - here = os.path.abspath(os.path.dirname(__file__)) - try: - flask.Flask(__name__, instance_path='instance') - except ValueError, e: - self.assert_('must be absolute' in str(e)) - else: - self.fail('Expected value error') - - app = flask.Flask(__name__, instance_path=here) - self.assertEqual(app.instance_path, here) - - def test_uninstalled_module_paths(self): - here = os.path.abspath(os.path.dirname(__file__)) - app = flask.Flask(__name__) - self.assertEqual(app.instance_path, os.path.join(here, 'instance')) - - def test_uninstalled_package_paths(self): - from blueprintapp import app - here = os.path.abspath(os.path.dirname(__file__)) - self.assertEqual(app.instance_path, os.path.join(here, '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 - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - 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 - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - 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 - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - 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 - try: - mod.app = flask.Flask(mod.__name__) - self.assertEqual(mod.app.instance_path, - os.path.join(expected_prefix, 'var', - 'myapp-instance')) - finally: - sys.modules['myapp'] = None - - -class JSONTestCase(FlaskTestCase): - - def test_json_bad_requests(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.assertEqual(rv.status_code, 400) - - def test_json_body_encoding(self): - app = flask.Flask(__name__) - app.testing = True - @app.route('/') - def index(): - return flask.request.json - - c = app.test_client() - resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), - content_type='application/json; charset=iso-8859-15') - assert resp.data == u'Hällo Wörld'.encode('utf-8') - - def test_jsonify(self): - d = dict(a=23, b=42, c=[1, 2, 3]) - app = flask.Flask(__name__) - @app.route('/kw') - def return_kwargs(): - return flask.jsonify(**d) - @app.route('/dict') - def return_dict(): - return flask.jsonify(d) - c = app.test_client() - for url in '/kw', '/dict': - rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data) == d - - def test_json_attr(self): - app = flask.Flask(__name__) - @app.route('/add', methods=['POST']) - def add(): - return unicode(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') - assert rv.data == '3' - - def test_template_escaping(self): - app = flask.Flask(__name__) - render = flask.render_template_string - with app.test_request_context(): - rv = render('{{ ""|tojson|safe }}') - assert rv == '"<\\/script>"' - rv = render('{{ "<\0/script>"|tojson|safe }}') - assert rv == '"<\\u0000\\/script>"' - - def test_modified_url_encoding(self): - class ModifiedRequest(flask.Request): - url_charset = 'euc-kr' - app = flask.Flask(__name__) - app.request_class = ModifiedRequest - app.url_map.charset = 'euc-kr' - - @app.route('/') - def index(): - return flask.request.args['foo'] - - rv = app.test_client().get(u'/?foo=ì •ìƒì²˜ë¦¬'.encode('euc-kr')) - assert rv.status_code == 200 - assert rv.data == u'ì •ìƒì²˜ë¦¬'.encode('utf-8') - - if not has_encoding('euc-kr'): - test_modified_url_encoding = None - - -class TemplatingTestCase(FlaskTestCase): - - def test_context_processing(self): - app = flask.Flask(__name__) - @app.context_processor - def context_processor(): - return {'injected_value': 42} - @app.route('/') - def index(): - return flask.render_template('context_template.html', value=23) - rv = app.test_client().get('/') - assert rv.data == '

23|42' - - def test_original_win(self): - app = flask.Flask(__name__) - @app.route('/') - def index(): - return flask.render_template_string('{{ config }}', config=42) - rv = app.test_client().get('/') - assert rv.data == '42' - - def test_standard_context(self): - app = flask.Flask(__name__) - app.secret_key = 'development key' - @app.route('/') - def index(): - flask.g.foo = 23 - flask.session['test'] = 'aha' - return flask.render_template_string(''' - {{ request.args.foo }} - {{ g.foo }} - {{ config.DEBUG }} - {{ session.test }} - ''') - rv = app.test_client().get('/?foo=42') - assert rv.data.split() == ['42', '23', 'False', 'aha'] - - def test_escaping(self): - text = '

Hello World!' - app = flask.Flask(__name__) - @app.route('/') - def index(): - return flask.render_template('escaping_template.html', text=text, - html=flask.Markup(text)) - lines = app.test_client().get('/').data.splitlines() - assert lines == [ - '<p>Hello World!', - '

Hello World!', - '

Hello World!', - '

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

Hello World!' - ] - - def test_no_escaping(self): - app = flask.Flask(__name__) - with app.test_request_context(): - assert flask.render_template_string('{{ foo }}', - foo='') == '' - assert flask.render_template('mail.txt', foo='') \ - == ' Mail' - - def test_macros(self): - app = flask.Flask(__name__) - with app.test_request_context(): - macro = flask.get_template_attribute('_macro.html', 'hello') - assert macro('World') == 'Hello World!' - - def test_template_filter(self): - app = flask.Flask(__name__) - @app.template_filter() - def my_reverse(s): - return s[::-1] - assert 'my_reverse' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['my_reverse'] == my_reverse - assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' - - def test_template_filter_with_name(self): - app = flask.Flask(__name__) - @app.template_filter('strrev') - def my_reverse(s): - return s[::-1] - assert 'strrev' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['strrev'] == my_reverse - assert app.jinja_env.filters['strrev']('abcd') == 'dcba' - - def test_template_filter_with_template(self): - app = flask.Flask(__name__) - @app.template_filter() - def super_reverse(s): - return s[::-1] - @app.route('/') - def index(): - return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') - assert rv.data == 'dcba' - - def test_template_filter_with_name_and_template(self): - app = flask.Flask(__name__) - @app.template_filter('super_reverse') - def my_reverse(s): - return s[::-1] - @app.route('/') - def index(): - return flask.render_template('template_filter.html', value='abcd') - rv = app.test_client().get('/') - assert rv.data == 'dcba' - - def test_custom_template_loader(self): - class MyFlask(flask.Flask): - def create_global_jinja_loader(self): - from jinja2 import DictLoader - return DictLoader({'index.html': 'Hello Custom World!'}) - app = MyFlask(__name__) - @app.route('/') - def index(): - return flask.render_template('index.html') - c = app.test_client() - rv = c.get('/') - assert rv.data == 'Hello Custom World!' - - -class ModuleTestCase(FlaskTestCase): - - @emits_module_deprecation_warning - def test_basic_module(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin', url_prefix='/admin') - @admin.route('/') - def admin_index(): - return 'admin index' - @admin.route('/login') - def admin_login(): - return 'admin login' - @admin.route('/logout') - def admin_logout(): - return 'admin logout' - @app.route('/') - def index(): - return 'the index' - app.register_module(admin) - c = app.test_client() - assert c.get('/').data == 'the index' - assert c.get('/admin/').data == 'admin index' - assert c.get('/admin/login').data == 'admin login' - assert c.get('/admin/logout').data == 'admin logout' - - @emits_module_deprecation_warning - def test_default_endpoint_name(self): - app = flask.Flask(__name__) - mod = flask.Module(__name__, 'frontend') - def index(): - return 'Awesome' - mod.add_url_rule('/', view_func=index) - app.register_module(mod) - rv = app.test_client().get('/') - assert rv.data == 'Awesome' - with app.test_request_context(): - assert flask.url_for('frontend.index') == '/' - - @emits_module_deprecation_warning - def test_request_processing(self): - catched = [] - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin', url_prefix='/admin') - @admin.before_request - def before_admin_request(): - catched.append('before-admin') - @admin.after_request - def after_admin_request(response): - catched.append('after-admin') - return response - @admin.route('/') - def admin_index(): - return 'the admin' - @app.before_request - def before_request(): - catched.append('before-app') - @app.after_request - def after_request(response): - catched.append('after-app') - return response - @app.route('/') - def index(): - return 'the index' - app.register_module(admin) - c = app.test_client() - - assert c.get('/').data == 'the index' - assert catched == ['before-app', 'after-app'] - del catched[:] - - assert c.get('/admin/').data == 'the admin' - assert catched == ['before-app', 'before-admin', - 'after-admin', 'after-app'] - - @emits_module_deprecation_warning - def test_context_processors(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin', url_prefix='/admin') - @app.context_processor - def inject_all_regualr(): - return {'a': 1} - @admin.context_processor - def inject_admin(): - return {'b': 2} - @admin.app_context_processor - def inject_all_module(): - return {'c': 3} - @app.route('/') - def index(): - return flask.render_template_string('{{ a }}{{ b }}{{ c }}') - @admin.route('/') - def admin_index(): - return flask.render_template_string('{{ a }}{{ b }}{{ c }}') - app.register_module(admin) - c = app.test_client() - assert c.get('/').data == '13' - assert c.get('/admin/').data == '123' - - @emits_module_deprecation_warning - def test_late_binding(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin') - @admin.route('/') - def index(): - return '42' - app.register_module(admin, url_prefix='/admin') - assert app.test_client().get('/admin/').data == '42' - - @emits_module_deprecation_warning - def test_error_handling(self): - app = flask.Flask(__name__) - admin = flask.Module(__name__, 'admin') - @admin.app_errorhandler(404) - def not_found(e): - return 'not found', 404 - @admin.app_errorhandler(500) - def internal_server_error(e): - return 'internal server error', 500 - @admin.route('/') - def index(): - flask.abort(404) - @admin.route('/error') - def error(): - 1 // 0 - app.register_module(admin) - c = app.test_client() - rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' - rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data - - def test_templates_and_static(self): - app = moduleapp - app.testing = True - c = app.test_client() - - rv = c.get('/') - assert rv.data == 'Hello from the Frontend' - rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' - rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' - - with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' - - with app.test_request_context(): - try: - flask.render_template('missing.html') - except TemplateNotFound, e: - assert e.name == 'missing.html' - else: - assert 0, 'expected exception' - - with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' - - def test_safe_access(self): - app = moduleapp - - with app.test_request_context(): - f = app.view_functions['admin.static'] - - try: - f('/etc/passwd') - except NotFound: - pass - else: - assert 0, 'expected exception' - try: - f('../__init__.py') - except NotFound: - pass - else: - assert 0, 'expected exception' - - # testcase for a security issue that may exist on windows systems - import os - import ntpath - old_path = os.path - os.path = ntpath - try: - try: - f('..\\__init__.py') - except NotFound: - pass - else: - assert 0, 'expected exception' - finally: - os.path = old_path - - @emits_module_deprecation_warning - def test_endpoint_decorator(self): - from werkzeug.routing import Submount, Rule - from flask import Module - - app = flask.Flask(__name__) - app.testing = True - app.url_map.add(Submount('/foo', [ - Rule('/bar', endpoint='bar'), - Rule('/', endpoint='index') - ])) - module = Module(__name__, __name__) - - @module.endpoint('bar') - def bar(): - return 'bar' - - @module.endpoint('index') - def index(): - return 'index' - - app.register_module(module) - - c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' - - -class BlueprintTestCase(FlaskTestCase): - - def test_blueprint_specific_error_handling(self): - frontend = flask.Blueprint('frontend', __name__) - backend = flask.Blueprint('backend', __name__) - sideend = flask.Blueprint('sideend', __name__) - - @frontend.errorhandler(403) - def frontend_forbidden(e): - return 'frontend says no', 403 - - @frontend.route('/frontend-no') - def frontend_no(): - flask.abort(403) - - @backend.errorhandler(403) - def backend_forbidden(e): - return 'backend says no', 403 - - @backend.route('/backend-no') - def backend_no(): - flask.abort(403) - - @sideend.route('/what-is-a-sideend') - def sideend_no(): - flask.abort(403) - - app = flask.Flask(__name__) - app.register_blueprint(frontend) - app.register_blueprint(backend) - app.register_blueprint(sideend) - - @app.errorhandler(403) - def app_forbidden(e): - return 'application itself says no', 403 - - c = app.test_client() - - assert c.get('/frontend-no').data == 'frontend says no' - assert c.get('/backend-no').data == 'backend says no' - assert c.get('/what-is-a-sideend').data == 'application itself says no' - - def test_blueprint_url_definitions(self): - bp = flask.Blueprint('test', __name__) - - @bp.route('/foo', defaults={'baz': 42}) - def foo(bar, baz): - return '%s/%d' % (bar, baz) - - @bp.route('/bar') - def bar(bar): - return unicode(bar) - - app = flask.Flask(__name__) - app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23}) - app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19}) - - c = app.test_client() - self.assertEqual(c.get('/1/foo').data, u'23/42') - self.assertEqual(c.get('/2/foo').data, u'19/42') - self.assertEqual(c.get('/1/bar').data, u'23') - self.assertEqual(c.get('/2/bar').data, u'19') - - def test_blueprint_url_processors(self): - bp = flask.Blueprint('frontend', __name__, url_prefix='/') - - @bp.url_defaults - def add_language_code(endpoint, values): - values.setdefault('lang_code', flask.g.lang_code) - - @bp.url_value_preprocessor - def pull_lang_code(endpoint, values): - flask.g.lang_code = values.pop('lang_code') - - @bp.route('/') - def index(): - return flask.url_for('.about') - - @bp.route('/about') - def about(): - return flask.url_for('.index') - - app = flask.Flask(__name__) - app.register_blueprint(bp) - - c = app.test_client() - - self.assertEqual(c.get('/de/').data, '/de/about') - self.assertEqual(c.get('/de/about').data, '/de/') - - def test_templates_and_static(self): - from blueprintapp import app - c = app.test_client() - - rv = c.get('/') - assert rv.data == 'Hello from the Frontend' - rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' - rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' - rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' - - with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' - - with app.test_request_context(): - try: - flask.render_template('missing.html') - except TemplateNotFound, e: - assert e.name == 'missing.html' - else: - assert 0, 'expected exception' - - with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' - - def test_templates_list(self): - from blueprintapp import app - templates = sorted(app.jinja_env.list_templates()) - self.assertEqual(templates, ['admin/index.html', - 'frontend/index.html']) - - def test_dotted_names(self): - frontend = flask.Blueprint('myapp.frontend', __name__) - backend = flask.Blueprint('myapp.backend', __name__) - - @frontend.route('/fe') - def frontend_index(): - return flask.url_for('myapp.backend.backend_index') - - @frontend.route('/fe2') - def frontend_page2(): - return flask.url_for('.frontend_index') - - @backend.route('/be') - def backend_index(): - return flask.url_for('myapp.frontend.frontend_index') - - app = flask.Flask(__name__) - app.register_blueprint(frontend) - app.register_blueprint(backend) - - c = app.test_client() - self.assertEqual(c.get('/fe').data.strip(), '/be') - self.assertEqual(c.get('/fe2').data.strip(), '/fe') - self.assertEqual(c.get('/be').data.strip(), '/fe') - - def test_empty_url_defaults(self): - bp = flask.Blueprint('bp', __name__) - - @bp.route('/', defaults={'page': 1}) - @bp.route('/page/') - def something(page): - return str(page) - - app = flask.Flask(__name__) - app.register_blueprint(bp) - - c = app.test_client() - self.assertEqual(c.get('/').data, '1') - self.assertEqual(c.get('/page/2').data, '2') - - -class SendfileTestCase(FlaskTestCase): - - def test_send_file_regular(self): - app = flask.Flask(__name__) - with app.test_request_context(): - rv = flask.send_file('static/index.html') - assert rv.direct_passthrough - assert rv.mimetype == 'text/html' - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - - def test_send_file_xsendfile(self): - app = flask.Flask(__name__) - app.use_x_sendfile = True - with app.test_request_context(): - rv = flask.send_file('static/index.html') - assert rv.direct_passthrough - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - assert rv.mimetype == 'text/html' - - def test_send_file_object(self): - app = flask.Flask(__name__) - with catch_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' - # mimetypes + etag - assert len(captured) == 2 - - app.use_x_sendfile = True - with catch_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - # mimetypes + etag - assert len(captured) == 2 - - app.use_x_sendfile = False - with app.test_request_context(): - with catch_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f) - assert rv.data == 'Test' - assert rv.mimetype == 'application/octet-stream' - # etags - assert len(captured) == 1 - with catch_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f, mimetype='text/plain') - assert rv.data == 'Test' - assert rv.mimetype == 'text/plain' - # etags - assert len(captured) == 1 - - app.use_x_sendfile = True - with catch_warnings() as captured: - with app.test_request_context(): - f = StringIO('Test') - rv = flask.send_file(f) - assert 'x-sendfile' not in rv.headers - # etags - assert len(captured) == 1 - - def test_attachment(self): - app = flask.Flask(__name__) - with catch_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f, as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - # mimetypes + etag - assert len(captured) == 2 - - with app.test_request_context(): - assert options['filename'] == 'index.html' - rv = flask.send_file('static/index.html', as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.html' - - with app.test_request_context(): - rv = flask.send_file(StringIO('Test'), as_attachment=True, - attachment_filename='index.txt', - add_etags=False) - assert rv.mimetype == 'text/plain' - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.txt' - - -class LoggingTestCase(FlaskTestCase): - - def test_logger_cache(self): - app = flask.Flask(__name__) - logger1 = app.logger - assert app.logger is logger1 - assert logger1.name == __name__ - app.logger_name = __name__ + '/test_logger_cache' - assert app.logger is not logger1 - - def test_debug_log(self): - app = flask.Flask(__name__) - app.debug = True - - @app.route('/') - def index(): - app.logger.warning('the standard library is dead') - app.logger.debug('this is a debug statement') - return '' - - @app.route('/exc') - def exc(): - 1/0 - - with app.test_client() as c: - with catch_stderr() as err: - c.get('/') - out = err.getvalue() - assert 'WARNING in flask_tests [' in out - assert 'flask_tests.py' in out - assert 'the standard library is dead' in out - assert 'this is a debug statement' in out - - with catch_stderr() as err: - try: - c.get('/exc') - except ZeroDivisionError: - pass - else: - assert False, 'debug log ate the exception' - - def test_exception_logging(self): - out = StringIO() - app = flask.Flask(__name__) - app.logger_name = 'flask_tests/test_exception_logging' - app.logger.addHandler(StreamHandler(out)) - - @app.route('/') - def index(): - 1/0 - - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert 'Internal Server Error' in rv.data - - err = out.getvalue() - assert 'Exception on / [GET]' in err - assert 'Traceback (most recent call last):' in err - assert '1/0' in err - assert 'ZeroDivisionError:' in err - - def test_processor_exceptions(self): - app = flask.Flask(__name__) - @app.before_request - def before_request(): - if trigger == 'before': - 1/0 - @app.after_request - def after_request(response): - if trigger == 'after': - 1/0 - return response - @app.route('/') - def index(): - return 'Foo' - @app.errorhandler(500) - def internal_server_error(e): - return 'Hello Server Error', 500 - for trigger in 'before', 'after': - rv = app.test_client().get('/') - assert rv.status_code == 500 - assert rv.data == 'Hello Server Error' - - -class ConfigTestCase(FlaskTestCase): - - def common_object_test(self, app): - assert app.secret_key == 'devkey' - assert app.config['TEST_KEY'] == 'foo' - assert 'ConfigTestCase' not in app.config - - def test_config_from_file(self): - app = flask.Flask(__name__) - app.config.from_pyfile('flask_tests.py') - self.common_object_test(app) - - def test_config_from_object(self): - app = flask.Flask(__name__) - app.config.from_object(__name__) - self.common_object_test(app) - - def test_config_from_class(self): - class Base(object): - TEST_KEY = 'foo' - class Test(Base): - SECRET_KEY = 'devkey' - app = flask.Flask(__name__) - app.config.from_object(Test) - self.common_object_test(app) - - def test_config_from_envvar(self): - import os - env = os.environ - try: - os.environ = {} - app = flask.Flask(__name__) - try: - app.config.from_envvar('FOO_SETTINGS') - except RuntimeError, e: - assert "'FOO_SETTINGS' is not set" in str(e) - else: - assert 0, 'expected exception' - assert not app.config.from_envvar('FOO_SETTINGS', silent=True) - - os.environ = {'FOO_SETTINGS': 'flask_tests.py'} - assert app.config.from_envvar('FOO_SETTINGS') - self.common_object_test(app) - finally: - os.environ = env - - def test_config_missing(self): - app = flask.Flask(__name__) - try: - app.config.from_pyfile('missing.cfg') - except IOError, e: - msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.cfg'") - else: - assert 0, 'expected config' - assert not app.config.from_pyfile('missing.cfg', silent=True) - - -class SubdomainTestCase(FlaskTestCase): - - def test_basic_support(self): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' - @app.route('/') - def normal_index(): - return 'normal index' - @app.route('/', subdomain='test') - def test_index(): - return 'test index' - - c = app.test_client() - rv = c.get('/', 'http://localhost/') - assert rv.data == 'normal index' - - rv = c.get('/', 'http://test.localhost/') - assert rv.data == 'test index' - - @emits_module_deprecation_warning - def test_module_static_path_subdomain(self): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'example.com' - from subdomaintestmodule import mod - app.register_module(mod) - c = app.test_client() - rv = c.get('/static/hello.txt', 'http://foo.example.com/') - assert rv.data.strip() == 'Hello Subdomain' - - def test_subdomain_matching(self): - app = flask.Flask(__name__) - app.config['SERVER_NAME'] = 'localhost' - @app.route('/', subdomain='') - def index(user): - return 'index for %s' % user - - c = app.test_client() - rv = c.get('/', 'http://mitsuhiko.localhost/') - assert rv.data == 'index for mitsuhiko' - - @emits_module_deprecation_warning - def test_module_subdomain_support(self): - app = flask.Flask(__name__) - mod = flask.Module(__name__, 'test', subdomain='testing') - app.config['SERVER_NAME'] = 'localhost' - - @mod.route('/test') - def test(): - return 'Test' - - @mod.route('/outside', subdomain='xtesting') - def bar(): - return 'Outside' - - app.register_module(mod) - - c = app.test_client() - rv = c.get('/test', 'http://testing.localhost/') - assert rv.data == 'Test' - rv = c.get('/outside', 'http://xtesting.localhost/') - assert rv.data == 'Outside' - - -class TestSignals(FlaskTestCase): - - def test_template_rendered(self): - app = flask.Flask(__name__) - - @app.route('/') - def index(): - return flask.render_template('simple_template.html', whiskey=42) - - recorded = [] - def record(sender, template, context): - recorded.append((template, context)) - - flask.template_rendered.connect(record, app) - try: - app.test_client().get('/') - assert len(recorded) == 1 - template, context = recorded[0] - assert template.name == 'simple_template.html' - assert context['whiskey'] == 42 - finally: - flask.template_rendered.disconnect(record, app) - - def test_request_signals(self): - app = flask.Flask(__name__) - calls = [] - - def before_request_signal(sender): - calls.append('before-signal') - - def after_request_signal(sender, response): - assert response.data == 'stuff' - calls.append('after-signal') - - @app.before_request - def before_request_handler(): - calls.append('before-handler') - - @app.after_request - def after_request_handler(response): - calls.append('after-handler') - response.data = 'stuff' - return response - - @app.route('/') - def index(): - calls.append('handler') - return 'ignored anyway' - - flask.request_started.connect(before_request_signal, app) - flask.request_finished.connect(after_request_signal, app) - - try: - rv = app.test_client().get('/') - assert rv.data == 'stuff' - - assert calls == ['before-signal', 'before-handler', - 'handler', 'after-handler', - 'after-signal'] - finally: - flask.request_started.disconnect(before_request_signal, app) - flask.request_finished.disconnect(after_request_signal, app) - - def test_request_exception_signal(self): - app = flask.Flask(__name__) - recorded = [] - - @app.route('/') - def index(): - 1/0 - - def record(sender, exception): - recorded.append(exception) - - flask.got_request_exception.connect(record, app) - try: - assert app.test_client().get('/').status_code == 500 - assert len(recorded) == 1 - assert isinstance(recorded[0], ZeroDivisionError) - finally: - flask.got_request_exception.disconnect(record, app) - - -class ViewTestCase(FlaskTestCase): - - def common_test(self, app): - c = app.test_client() - - self.assertEqual(c.get('/').data, 'GET') - self.assertEqual(c.post('/').data, 'POST') - self.assertEqual(c.put('/').status_code, 405) - meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) - self.assertEqual(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST']) - - def test_basic_view(self): - app = flask.Flask(__name__) - - 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')) - self.common_test(app) - - def test_method_based_view(self): - app = flask.Flask(__name__) - - class Index(flask.views.MethodView): - def get(self): - return 'GET' - def post(self): - return 'POST' - - app.add_url_rule('/', view_func=Index.as_view('index')) - - self.common_test(app) - - def test_view_patching(self): - app = flask.Flask(__name__) - - class Index(flask.views.MethodView): - def get(self): - 1/0 - def post(self): - 1/0 - - class Other(Index): - def get(self): - return 'GET' - def post(self): - return 'POST' - - view = Index.as_view('index') - view.view_class = Other - app.add_url_rule('/', view_func=view) - self.common_test(app) - - def test_view_inheritance(self): - app = flask.Flask(__name__) - - class Index(flask.views.MethodView): - def get(self): - return 'GET' - def post(self): - return 'POST' - - class BetterIndex(Index): - def delete(self): - return 'DELETE' - - app.add_url_rule('/', view_func=BetterIndex.as_view('index')) - c = app.test_client() - - meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow']) - self.assertEqual(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST']) - - def test_view_decorators(self): - app = flask.Flask(__name__) - - def add_x_parachute(f): - def new_function(*args, **kwargs): - resp = flask.make_response(f(*args, **kwargs)) - resp.headers['X-Parachute'] = 'awesome' - return resp - return new_function - - class Index(flask.views.View): - decorators = [add_x_parachute] - def dispatch_request(self): - return 'Awesome' - - app.add_url_rule('/', view_func=Index.as_view('index')) - c = app.test_client() - rv = c.get('/') - self.assertEqual(rv.headers['X-Parachute'], 'awesome') - self.assertEqual(rv.data, 'Awesome') - - -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() - assert c.get('/').data == '42' - assert len(log) == 1 - assert 'init_jinja_globals' in str(log[0]['message']) - - -def suite(): - from minitwit_tests import MiniTwitTestCase - from flaskr_tests import FlaskrTestCase - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(ContextTestCase)) - suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase)) - suite.addTest(unittest.makeSuite(TemplatingTestCase)) - suite.addTest(unittest.makeSuite(ModuleTestCase)) - suite.addTest(unittest.makeSuite(BlueprintTestCase)) - suite.addTest(unittest.makeSuite(SendfileTestCase)) - suite.addTest(unittest.makeSuite(LoggingTestCase)) - suite.addTest(unittest.makeSuite(ConfigTestCase)) - suite.addTest(unittest.makeSuite(SubdomainTestCase)) - suite.addTest(unittest.makeSuite(ViewTestCase)) - suite.addTest(unittest.makeSuite(DeprecationsTestCase)) - suite.addTest(unittest.makeSuite(TestToolsTestCase)) - suite.addTest(unittest.makeSuite(InstanceTestCase)) - if flask.json_available: - suite.addTest(unittest.makeSuite(JSONTestCase)) - if flask.signals_available: - suite.addTest(unittest.makeSuite(TestSignals)) - suite.addTest(unittest.makeSuite(MiniTwitTestCase)) - suite.addTest(unittest.makeSuite(FlaskrTestCase)) - return suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') From 12d74be7d7f62400663911d94783320bdfba86d4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:24:10 +0100 Subject: [PATCH 0120/3143] Added better test runner --- run-tests.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 run-tests.py diff --git a/run-tests.py b/run-tests.py new file mode 100644 index 00000000..96db7f9a --- /dev/null +++ b/run-tests.py @@ -0,0 +1,57 @@ +import sys +import unittest +from unittest.loader import TestLoader +from flask.testsuite import suite + +common_prefix = suite.__module__ + '.' + + +def find_all_tests(): + suites = [suite()] + while suites: + s = suites.pop() + try: + suites.extend(s) + except TypeError: + yield s + + +def find_all_tests_with_name(): + for testcase in find_all_tests(): + yield testcase, '%s.%s.%s' % ( + testcase.__class__.__module__, + testcase.__class__.__name__, + testcase._testMethodName + ) + + +class BetterLoader(TestLoader): + + def loadTestsFromName(self, name, module=None): + if name == 'suite': + return suite() + for testcase, testname in find_all_tests_with_name(): + if testname == name: + return testcase + if testname.startswith(common_prefix): + if testname[len(common_prefix):] == name: + return testcase + + all_results = [] + for testcase, testname in find_all_tests_with_name(): + if testname.endswith('.' + name): + all_results.append((testcase, testname)) + + if len(all_results) == 1: + return all_results[0][0] + elif not len(all_results): + error = 'could not find testcase "%s"' % name + else: + error = 'Too many matches: for "%s"\n%s' % \ + (name, '\n'.join(' - ' + n for c, n in all_results)) + + print >> sys.stderr, 'Error: %s' % error + sys.exit(1) + + +unittest.main(testLoader=BetterLoader(), defaultTest='suite') From 2b830af2efb4523fa2e2e9b9dfcc33212effe2e6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:24:42 +0100 Subject: [PATCH 0121/3143] Use the better test runner in the makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7b14422f..43f47275 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: clean-pyc test test: - python setup.py test + python run-tests.py audit: python setup.py audit From 3069e2d7f73f80ebc342029616a5cbc5381cfdf1 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:38:43 +0100 Subject: [PATCH 0122/3143] Fight the generic asserts! --- flask/testsuite/basic.py | 212 ++++++++++++++++---------------- flask/testsuite/blueprints.py | 74 +++++------ flask/testsuite/config.py | 4 +- flask/testsuite/deprecations.py | 4 +- flask/testsuite/helpers.py | 78 ++++++------ flask/testsuite/signals.py | 18 +-- flask/testsuite/templating.py | 26 ++-- flask/testsuite/testing.py | 8 +- 8 files changed, 212 insertions(+), 212 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index c55881e5..8c2be901 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -26,8 +26,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(): return 'Hello World' rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] - assert rv.data == '' + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) + self.assert_equal(rv.data, '') def test_options_on_multiple_rules(self): app = flask.Flask(__name__) @@ -38,7 +38,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index_put(): return 'Aha!' rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']) def test_options_handling_disabled(self): app = flask.Flask(__name__) @@ -47,7 +47,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): index.provide_automatic_options = False app.route('/')(index) rv = app.test_client().open('/', method='OPTIONS') - assert rv.status_code == 405 + self.assert_equal(rv.status_code, 405) app = flask.Flask(__name__) def index2(): @@ -55,7 +55,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): index2.provide_automatic_options = True app.route('/', methods=['OPTIONS'])(index2) rv = app.test_client().open('/', method='OPTIONS') - assert sorted(rv.allow) == ['OPTIONS'] + self.assert_equal(sorted(rv.allow), ['OPTIONS']) def test_request_dispatching(self): app = flask.Flask(__name__) @@ -67,18 +67,18 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.request.method c = app.test_client() - assert c.get('/').data == 'GET' + self.assert_equal(c.get('/').data, 'GET') rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' + self.assert_equal(c.post('/more').data, 'POST') + self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) def test_url_mapping(self): app = flask.Flask(__name__) @@ -91,18 +91,18 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.add_url_rule('/more', 'more', more, methods=['GET', 'POST']) c = app.test_client() - assert c.get('/').data == 'GET' + self.assert_equal(c.get('/').data, 'GET') rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert not rv.data # head truncates - assert c.post('/more').data == 'POST' - assert c.get('/more').data == 'GET' + self.assert_equal(c.post('/more').data, 'POST') + self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] + self.assert_equal(rv.status_code, 405) + self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST']) def test_werkzeug_routing(self): from werkzeug.routing import Submount, Rule @@ -119,8 +119,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): app.view_functions['index'] = index c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' + self.assert_equal(c.get('/foo/').data, 'index') + self.assert_equal(c.get('/foo/bar').data, 'bar') def test_endpoint_decorator(self): from werkzeug.routing import Submount, Rule @@ -139,8 +139,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return 'index' c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' + self.assert_equal(c.get('/foo/').data, 'index') + self.assert_equal(c.get('/foo/bar').data, 'bar') def test_session(self): app = flask.Flask(__name__) @@ -154,8 +154,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): return flask.session['value'] c = app.test_client() - assert c.post('/set', data={'value': '42'}).data == 'value set' - assert c.get('/get').data == '42' + self.assert_equal(c.post('/set', data={'value': '42'}).data, 'value set') + self.assert_equal(c.get('/get').data, '42') def test_session_using_server_name(self): app = flask.Flask(__name__) @@ -241,12 +241,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime - assert expires.year == expected.year - assert expires.month == expected.month - assert expires.day == expected.day + self.assert_equal(expires.year, expected.year) + self.assert_equal(expires.month, expected.month) + self.assert_equal(expires.day, expected.day) rv = client.get('/test') - assert rv.data == 'True' + self.assert_equal(rv.data, 'True') permanent = False rv = app.test_client().get('/') @@ -264,7 +264,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session.modified = False flask.flash('Zip') assert flask.session.modified - assert list(flask.get_flashed_messages()) == ['Zap', 'Zip'] + self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): app = flask.Flask(__name__) @@ -280,16 +280,16 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.route('/test') def test(): messages = flask.get_flashed_messages(with_categories=True) - assert len(messages) == 3 - assert messages[0] == ('message', u'Hello World') - assert messages[1] == ('error', u'Hello World') - assert messages[2] == ('warning', flask.Markup(u'Testing')) + 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() - assert len(messages) == 3 - assert messages[0] == u'Hello World' - assert messages[1] == u'Hello World' - assert messages[2] == flask.Markup(u'Testing') + 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')) c = app.test_client() c.get('/') @@ -314,7 +314,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): assert 'after' not in evts rv = app.test_client().get('/').data assert 'after' in evts - assert rv == 'request|after' + self.assert_equal(rv, 'request|after') def test_teardown_request_handler(self): called = [] @@ -327,9 +327,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): def root(): return "Response" rv = app.test_client().get('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert 'Response' in rv.data - assert len(called) == 1 + self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): called = [] @@ -343,16 +343,16 @@ class BasicFunctionalityTestCase(FlaskTestCase): def root(): return "Response" rv = app.test_client().get('/') - assert rv.status_code == 200 + self.assert_equal(rv.status_code, 200) assert 'Response' in rv.data - assert len(called) == 1 + self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): called = [] app = flask.Flask(__name__) @app.teardown_request def teardown_request1(exc): - assert type(exc) == ZeroDivisionError + self.assert_equal(type(exc), ZeroDivisionError) called.append(True) # This raises a new error and blows away sys.exc_info(), so we can # test that all teardown_requests get passed the same original @@ -363,7 +363,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): pass @app.teardown_request def teardown_request2(exc): - assert type(exc) == ZeroDivisionError + self.assert_equal(type(exc), ZeroDivisionError) called.append(True) # This raises a new error and blows away sys.exc_info(), so we can # test that all teardown_requests get passed the same original @@ -376,9 +376,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): def fails(): 1/0 rv = app.test_client().get('/') - assert rv.status_code == 500 + self.assert_equal(rv.status_code, 500) assert 'Internal Server Error' in rv.data - assert len(called) == 2 + self.assert_equal(len(called), 2) def test_before_after_request_order(self): called = [] @@ -407,8 +407,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(): return '42' rv = app.test_client().get('/') - assert rv.data == '42' - assert called == [1, 2, 3, 4, 5, 6] + self.assert_equal(rv.data, '42') + self.assert_equal(called, [1, 2, 3, 4, 5, 6]) def test_error_handling(self): app = flask.Flask(__name__) @@ -426,11 +426,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1 // 0 c = app.test_client() rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'not found') rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data + self.assert_equal(rv.status_code, 500) + self.assert_equal('internal server error', rv.data) def test_before_request_and_routing_errors(self): app = flask.Flask(__name__) @@ -441,8 +441,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): def return_something(error): return flask.g.something, 404 rv = app.test_client().get('/') - assert rv.status_code == 404 - assert rv.data == 'value' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'value') def test_user_error_handling(self): class MyException(Exception): @@ -458,7 +458,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): raise MyException() c = app.test_client() - assert c.get('/').data == '42' + self.assert_equal(c.get('/').data, '42') def test_trapping_of_bad_request_key_errors(self): app = flask.Flask(__name__) @@ -467,7 +467,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def fail(): flask.request.form['missing_key'] c = app.test_client() - assert c.get('/fail').status_code == 400 + self.assert_equal(c.get('/fail').status_code, 400) app.config['TRAP_BAD_REQUEST_ERRORS'] = True c = app.test_client() @@ -523,9 +523,9 @@ class BasicFunctionalityTestCase(FlaskTestCase): ctx = app.test_request_context() ctx.push() - assert buffer == [] + self.assert_equal(buffer, []) ctx.pop() - assert buffer == [None] + self.assert_equal(buffer, [None]) def test_response_creation(self): app = flask.Flask(__name__) @@ -539,31 +539,31 @@ class BasicFunctionalityTestCase(FlaskTestCase): def from_tuple(): return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain' c = app.test_client() - assert c.get('/unicode').data == u'Hällo Wörld'.encode('utf-8') - assert c.get('/string').data == u'Hällo Wörld'.encode('utf-8') + 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') - assert rv.data == 'Meh' - assert rv.headers['X-Foo'] == 'Testing' - assert rv.status_code == 400 - assert rv.mimetype == 'text/plain' + self.assert_equal(rv.data, 'Meh') + self.assert_equal(rv.headers['X-Foo'], 'Testing') + self.assert_equal(rv.status_code, 400) + self.assert_equal(rv.mimetype, 'text/plain') def test_make_response(self): app = flask.Flask(__name__) with app.test_request_context(): rv = flask.make_response() - assert rv.status_code == 200 - assert rv.data == '' - assert rv.mimetype == 'text/html' + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, '') + self.assert_equal(rv.mimetype, 'text/html') rv = flask.make_response('Awesome') - assert rv.status_code == 200 - assert rv.data == 'Awesome' - assert rv.mimetype == 'text/html' + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, 'Awesome') + self.assert_equal(rv.mimetype, 'text/html') rv = flask.make_response('W00t', 404) - assert rv.status_code == 404 - assert rv.data == 'W00t' - assert rv.mimetype == 'text/html' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'W00t') + self.assert_equal(rv.mimetype, 'text/html') def test_url_generation(self): app = flask.Flask(__name__) @@ -571,7 +571,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): def hello(): pass with app.test_request_context(): - assert flask.url_for('hello', name='test x') == '/hello/test%20x' + self.assert_equal(flask.url_for('hello', name='test x'), '/hello/test%20x') assert flask.url_for('hello', name='test x', _external=True) \ == 'http://localhost/hello/test%20x' @@ -589,13 +589,13 @@ class BasicFunctionalityTestCase(FlaskTestCase): def index(args): return '|'.join(args) c = app.test_client() - assert c.get('/1,2,3').data == '1|2|3' + self.assert_equal(c.get('/1,2,3').data, '1|2|3') def test_static_files(self): app = flask.Flask(__name__) rv = app.test_client().get('/static/index.html') - assert rv.status_code == 200 - assert rv.data.strip() == '

Hello World!

' + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data.strip(), '

Hello World!

') with app.test_request_context(): assert flask.url_for('static', filename='index.html') \ == '/static/index.html' @@ -608,7 +608,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: app.test_client().get('/') except ValueError, e: - assert str(e) == 'View function did not return a response' + self.assert_equal(str(e), 'View function did not return a response') pass else: assert "Expected ValueError" @@ -632,19 +632,19 @@ class BasicFunctionalityTestCase(FlaskTestCase): return None with app.test_request_context('/'): - assert flask.url_for('index', _external=True) == 'http://localhost.localdomain:5000/' + self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/') with app.test_request_context('/'): - assert flask.url_for('sub', _external=True) == 'http://foo.localhost.localdomain:5000/' + 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: assert isinstance(e, ValueError) - assert str(e) == "the server name provided " + \ + self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:5000') does not match the " + \ - "server name from the WSGI environment ('localhost')", str(e) + "server name from the WSGI environment ('localhost')") try: app.config.update(SERVER_NAME='localhost') @@ -679,7 +679,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: rv = app.test_client().get('/') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -687,7 +687,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: rv = app.test_client().get('/', 'http://localhost.localdomain:5000') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -695,7 +695,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: rv = app.test_client().get('/', 'https://localhost.localdomain:5000') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -704,7 +704,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: app.config.update(SERVER_NAME='localhost.localdomain') rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -713,23 +713,23 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: app.config.update(SERVER_NAME='localhost.localdomain:443') rv = app.test_client().get('/', 'https://localhost.localdomain') - assert rv.data == 'Foo' + self.assert_equal(rv.data, 'Foo') except ValueError, e: - assert str(e) == "the server name provided " + \ + self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:443') does not match the " + \ - "server name from the WSGI environment ('localhost.localdomain')", str(e) + "server name from the WSGI environment ('localhost.localdomain')") try: app.config.update(SERVER_NAME='localhost.localdomain') app.test_client().get('/', 'http://foo.localhost') except ValueError, e: - assert str(e) == "the server name provided " + \ + self.assert_equal(str(e), "the server name provided " + \ "('localhost.localdomain') does not match the " + \ - "server name from the WSGI environment ('foo.localhost')", str(e) + "server name from the WSGI environment ('foo.localhost')") try: rv = app.test_client().get('/', 'http://foo.localhost.localdomain') - assert rv.data == 'Foo SubDomain' + self.assert_equal(rv.data, 'Foo SubDomain') except ValueError, e: raise ValueError( "No ValueError exception should have been raised \"%s\"" % e @@ -751,7 +751,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): else: self.fail('expected exception') else: - assert c.get('/').status_code == 500 + self.assert_equal(c.get('/').status_code, 500) # we have to run this test in an isolated thread because if the # debug flag is set to true and an exception happens the context is @@ -779,7 +779,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): c = app.test_client() rv = c.post('/accept', data={'myfile': 'foo' * 100}) - assert rv.data == '42' + self.assert_equal(rv.data, '42') def test_url_processors(self): app = flask.Flask(__name__) @@ -886,9 +886,9 @@ class ContextTestCase(FlaskTestCase): return flask.request.url with app.test_request_context('/?name=World'): - assert index() == 'Hello World!' + self.assert_equal(index(), 'Hello World!') with app.test_request_context('/meh'): - assert meh() == 'http://localhost/meh' + self.assert_equal(meh(), 'http://localhost/meh') assert flask._request_ctx_stack.top is None def test_context_test(self): @@ -911,7 +911,7 @@ class ContextTestCase(FlaskTestCase): ctx = app.test_request_context('/?name=World') ctx.push() - assert index() == 'Hello World!' + self.assert_equal(index(), 'Hello World!') ctx.pop() try: index() @@ -935,10 +935,10 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://localhost/') - assert rv.data == 'normal index' + self.assert_equal(rv.data, 'normal index') rv = c.get('/', 'http://test.localhost/') - assert rv.data == 'test index' + self.assert_equal(rv.data, 'test index') @emits_module_deprecation_warning def test_module_static_path_subdomain(self): @@ -948,7 +948,7 @@ class SubdomainTestCase(FlaskTestCase): app.register_module(mod) c = app.test_client() rv = c.get('/static/hello.txt', 'http://foo.example.com/') - assert rv.data.strip() == 'Hello Subdomain' + self.assert_equal(rv.data.strip(), 'Hello Subdomain') def test_subdomain_matching(self): app = flask.Flask(__name__) @@ -959,7 +959,7 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/', 'http://mitsuhiko.localhost/') - assert rv.data == 'index for mitsuhiko' + self.assert_equal(rv.data, 'index for mitsuhiko') @emits_module_deprecation_warning def test_module_subdomain_support(self): @@ -979,9 +979,9 @@ class SubdomainTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/test', 'http://testing.localhost/') - assert rv.data == 'Test' + self.assert_equal(rv.data, 'Test') rv = c.get('/outside', 'http://xtesting.localhost/') - assert rv.data == 'Outside' + self.assert_equal(rv.data, 'Outside') def suite(): diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 93122ac5..fdd63fee 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -43,10 +43,10 @@ class ModuleTestCase(FlaskTestCase): return 'the index' app.register_module(admin) c = app.test_client() - assert c.get('/').data == 'the index' - assert c.get('/admin/').data == 'admin index' - assert c.get('/admin/login').data == 'admin login' - assert c.get('/admin/logout').data == 'admin logout' + 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') @emits_module_deprecation_warning def test_default_endpoint_name(self): @@ -57,9 +57,9 @@ class ModuleTestCase(FlaskTestCase): mod.add_url_rule('/', view_func=index) app.register_module(mod) rv = app.test_client().get('/') - assert rv.data == 'Awesome' + self.assert_equal(rv.data, 'Awesome') with app.test_request_context(): - assert flask.url_for('frontend.index') == '/' + self.assert_equal(flask.url_for('frontend.index'), '/') @emits_module_deprecation_warning def test_request_processing(self): @@ -89,13 +89,13 @@ class ModuleTestCase(FlaskTestCase): app.register_module(admin) c = app.test_client() - assert c.get('/').data == 'the index' - assert catched == ['before-app', 'after-app'] + self.assert_equal(c.get('/').data, 'the index') + self.assert_equal(catched, ['before-app', 'after-app']) del catched[:] - assert c.get('/admin/').data == 'the admin' - assert catched == ['before-app', 'before-admin', - 'after-admin', 'after-app'] + self.assert_equal(c.get('/admin/').data, 'the admin') + self.assert_equal(catched, ['before-app', 'before-admin', + 'after-admin', 'after-app']) @emits_module_deprecation_warning def test_context_processors(self): @@ -118,8 +118,8 @@ class ModuleTestCase(FlaskTestCase): return flask.render_template_string('{{ a }}{{ b }}{{ c }}') app.register_module(admin) c = app.test_client() - assert c.get('/').data == '13' - assert c.get('/admin/').data == '123' + self.assert_equal(c.get('/').data, '13') + self.assert_equal(c.get('/admin/').data, '123') @emits_module_deprecation_warning def test_late_binding(self): @@ -129,7 +129,7 @@ class ModuleTestCase(FlaskTestCase): def index(): return '42' app.register_module(admin, url_prefix='/admin') - assert app.test_client().get('/admin/').data == '42' + self.assert_equal(app.test_client().get('/admin/').data, '42') @emits_module_deprecation_warning def test_error_handling(self): @@ -150,11 +150,11 @@ class ModuleTestCase(FlaskTestCase): app.register_module(admin) c = app.test_client() rv = c.get('/') - assert rv.status_code == 404 - assert rv.data == 'not found' + self.assert_equal(rv.status_code, 404) + self.assert_equal(rv.data, 'not found') rv = c.get('/error') - assert rv.status_code == 500 - assert 'internal server error' == rv.data + self.assert_equal(rv.status_code, 500) + self.assert_equal('internal server error', rv.data) def test_templates_and_static(self): app = moduleapp @@ -162,15 +162,15 @@ class ModuleTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') - assert rv.data == 'Hello from the Frontend' + self.assert_equal(rv.data, 'Hello from the Frontend') rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' + self.assert_equal(rv.data.strip(), 'Admin File') rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' + self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): assert flask.url_for('admin.static', filename='test.txt') \ @@ -180,12 +180,12 @@ class ModuleTestCase(FlaskTestCase): try: flask.render_template('missing.html') except TemplateNotFound, e: - assert e.name == 'missing.html' + self.assert_equal(e.name, 'missing.html') else: assert 0, 'expected exception' with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' + self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') def test_safe_access(self): app = moduleapp @@ -245,8 +245,8 @@ class ModuleTestCase(FlaskTestCase): app.register_module(module) c = app.test_client() - assert c.get('/foo/').data == 'index' - assert c.get('/foo/bar').data == 'bar' + self.assert_equal(c.get('/foo/').data, 'index') + self.assert_equal(c.get('/foo/bar').data, 'bar') class BlueprintTestCase(FlaskTestCase): @@ -287,9 +287,9 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() - assert c.get('/frontend-no').data == 'frontend says no' - assert c.get('/backend-no').data == 'backend says no' - assert c.get('/what-is-a-sideend').data == 'application itself says no' + 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') def test_blueprint_url_definitions(self): bp = flask.Blueprint('test', __name__) @@ -344,15 +344,15 @@ class BlueprintTestCase(FlaskTestCase): c = app.test_client() rv = c.get('/') - assert rv.data == 'Hello from the Frontend' + self.assert_equal(rv.data, 'Hello from the Frontend') rv = c.get('/admin/') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/index2') - assert rv.data == 'Hello from the Admin' + self.assert_equal(rv.data, 'Hello from the Admin') rv = c.get('/admin/static/test.txt') - assert rv.data.strip() == 'Admin File' + self.assert_equal(rv.data.strip(), 'Admin File') rv = c.get('/admin/static/css/test.css') - assert rv.data.strip() == '/* nested file */' + self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): assert flask.url_for('admin.static', filename='test.txt') \ @@ -362,12 +362,12 @@ class BlueprintTestCase(FlaskTestCase): try: flask.render_template('missing.html') except TemplateNotFound, e: - assert e.name == 'missing.html' + self.assert_equal(e.name, 'missing.html') else: assert 0, 'expected exception' with flask.Flask(__name__).test_request_context(): - assert flask.render_template('nested/nested.txt') == 'I\'m nested' + self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') def test_templates_list(self): from blueprintapp import app diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index c8bf4687..2b689bc0 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -23,8 +23,8 @@ SECRET_KEY = 'devkey' class ConfigTestCase(FlaskTestCase): def common_object_test(self, app): - assert app.secret_key == 'devkey' - assert app.config['TEST_KEY'] == 'foo' + self.assert_equal(app.secret_key, 'devkey') + self.assert_equal(app.config['TEST_KEY'], 'foo') assert 'ConfigTestCase' not in app.config def test_config_from_file(self): diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py index d691b1dd..531f7f82 100644 --- a/flask/testsuite/deprecations.py +++ b/flask/testsuite/deprecations.py @@ -27,8 +27,8 @@ class DeprecationsTestCase(FlaskTestCase): return app.jinja_env.globals['foo'] c = app.test_client() - assert c.get('/').data == '42' - assert len(log) == 1 + self.assert_equal(c.get('/').data, '42') + self.assert_equal(len(log), 1) assert 'init_jinja_globals' in str(log[0]['message']) diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index faea9c8d..50494640 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -47,7 +47,7 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'), content_type='application/json; charset=iso-8859-15') - assert resp.data == u'Hällo Wörld'.encode('utf-8') + self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8')) def test_jsonify(self): d = dict(a=23, b=42, c=[1, 2, 3]) @@ -61,8 +61,8 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() for url in '/kw', '/dict': rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data) == d + self.assert_equal(rv.mimetype, 'application/json') + self.assert_equal(flask.json.loads(rv.data), d) def test_json_attr(self): app = flask.Flask(__name__) @@ -72,16 +72,16 @@ class JSONTestCase(FlaskTestCase): c = app.test_client() rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}), content_type='application/json') - assert rv.data == '3' + self.assert_equal(rv.data, '3') def test_template_escaping(self): app = flask.Flask(__name__) render = flask.render_template_string with app.test_request_context(): rv = render('{{ ""|tojson|safe }}') - assert rv == '"<\\/script>"' + self.assert_equal(rv, '"<\\/script>"') rv = render('{{ "<\0/script>"|tojson|safe }}') - assert rv == '"<\\u0000\\/script>"' + self.assert_equal(rv, '"<\\u0000\\/script>"') def test_modified_url_encoding(self): class ModifiedRequest(flask.Request): @@ -95,8 +95,8 @@ class JSONTestCase(FlaskTestCase): return flask.request.args['foo'] rv = app.test_client().get(u'/?foo=ì •ìƒì²˜ë¦¬'.encode('euc-kr')) - assert rv.status_code == 200 - assert rv.data == u'ì •ìƒì²˜ë¦¬'.encode('utf-8') + self.assert_equal(rv.status_code, 200) + self.assert_equal(rv.data, u'ì •ìƒì²˜ë¦¬'.encode('utf-8')) if not has_encoding('euc-kr'): test_modified_url_encoding = None @@ -109,9 +109,9 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): rv = flask.send_file('static/index.html') assert rv.direct_passthrough - assert rv.mimetype == 'text/html' + self.assert_equal(rv.mimetype, 'text/html') with app.open_resource('static/index.html') as f: - assert rv.data == f.read() + self.assert_equal(rv.data, f.read()) def test_send_file_xsendfile(self): app = flask.Flask(__name__) @@ -120,9 +120,9 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file('static/index.html') assert rv.direct_passthrough assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - assert rv.mimetype == 'text/html' + self.assert_equal(rv.headers['x-sendfile'], + os.path.join(app.root_path, 'static/index.html')) + self.assert_equal(rv.mimetype, 'text/html') def test_send_file_object(self): app = flask.Flask(__name__) @@ -131,39 +131,39 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' + self.assert_equal(rv.data, f.read()) + self.assert_equal(rv.mimetype, 'text/html') # mimetypes + etag - assert len(captured) == 2 + self.assert_equal(len(captured), 2) app.use_x_sendfile = True with catch_warnings() as captured: with app.test_request_context(): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f) - assert rv.mimetype == 'text/html' + self.assert_equal(rv.mimetype, 'text/html') assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') + self.assert_equal(rv.headers['x-sendfile'], + os.path.join(app.root_path, 'static/index.html')) # mimetypes + etag - assert len(captured) == 2 + self.assert_equal(len(captured), 2) app.use_x_sendfile = False with app.test_request_context(): with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f) - assert rv.data == 'Test' - assert rv.mimetype == 'application/octet-stream' + self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.mimetype, 'application/octet-stream') # etags - assert len(captured) == 1 + self.assert_equal(len(captured), 1) with catch_warnings() as captured: f = StringIO('Test') rv = flask.send_file(f, mimetype='text/plain') - assert rv.data == 'Test' - assert rv.mimetype == 'text/plain' + self.assert_equal(rv.data, 'Test') + self.assert_equal(rv.mimetype, 'text/plain') # etags - assert len(captured) == 1 + self.assert_equal(len(captured), 1) app.use_x_sendfile = True with catch_warnings() as captured: @@ -172,7 +172,7 @@ class SendfileTestCase(FlaskTestCase): rv = flask.send_file(f) assert 'x-sendfile' not in rv.headers # etags - assert len(captured) == 1 + self.assert_equal(len(captured), 1) def test_attachment(self): app = flask.Flask(__name__) @@ -181,25 +181,25 @@ class SendfileTestCase(FlaskTestCase): f = open(os.path.join(app.root_path, 'static/index.html')) rv = flask.send_file(f, as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' + self.assert_equal(value, 'attachment') # mimetypes + etag - assert len(captured) == 2 + self.assert_equal(len(captured), 2) with app.test_request_context(): - assert options['filename'] == 'index.html' + self.assert_equal(options['filename'], 'index.html') rv = flask.send_file('static/index.html', as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.html' + self.assert_equal(value, 'attachment') + self.assert_equal(options['filename'], 'index.html') with app.test_request_context(): rv = flask.send_file(StringIO('Test'), as_attachment=True, attachment_filename='index.txt', add_etags=False) - assert rv.mimetype == 'text/plain' + self.assert_equal(rv.mimetype, 'text/plain') value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - assert options['filename'] == 'index.txt' + self.assert_equal(value, 'attachment') + self.assert_equal(options['filename'], 'index.txt') class LoggingTestCase(FlaskTestCase): @@ -208,7 +208,7 @@ class LoggingTestCase(FlaskTestCase): app = flask.Flask(__name__) logger1 = app.logger assert app.logger is logger1 - assert logger1.name == __name__ + self.assert_equal(logger1.name, __name__) app.logger_name = __name__ + '/test_logger_cache' assert app.logger is not logger1 @@ -254,7 +254,7 @@ class LoggingTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') - assert rv.status_code == 500 + self.assert_equal(rv.status_code, 500) assert 'Internal Server Error' in rv.data err = out.getvalue() @@ -282,8 +282,8 @@ class LoggingTestCase(FlaskTestCase): return 'Hello Server Error', 500 for trigger in 'before', 'after': rv = app.test_client().get('/') - assert rv.status_code == 500 - assert rv.data == 'Hello Server Error' + self.assert_equal(rv.status_code, 500) + self.assert_equal(rv.data, 'Hello Server Error') def suite(): diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index e55807b3..d9054a47 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -29,10 +29,10 @@ class SignalsTestCase(FlaskTestCase): flask.template_rendered.connect(record, app) try: app.test_client().get('/') - assert len(recorded) == 1 + self.assert_equal(len(recorded), 1) template, context = recorded[0] - assert template.name == 'simple_template.html' - assert context['whiskey'] == 42 + self.assert_equal(template.name, 'simple_template.html') + self.assert_equal(context['whiskey'], 42) finally: flask.template_rendered.disconnect(record, app) @@ -44,7 +44,7 @@ class SignalsTestCase(FlaskTestCase): calls.append('before-signal') def after_request_signal(sender, response): - assert response.data == 'stuff' + self.assert_equal(response.data, 'stuff') calls.append('after-signal') @app.before_request @@ -67,11 +67,11 @@ class SignalsTestCase(FlaskTestCase): try: rv = app.test_client().get('/') - assert rv.data == 'stuff' + self.assert_equal(rv.data, 'stuff') - assert calls == ['before-signal', 'before-handler', + self.assert_equal(calls, ['before-signal', 'before-handler', 'handler', 'after-handler', - 'after-signal'] + 'after-signal']) finally: flask.request_started.disconnect(before_request_signal, app) flask.request_finished.disconnect(after_request_signal, app) @@ -89,8 +89,8 @@ class SignalsTestCase(FlaskTestCase): flask.got_request_exception.connect(record, app) try: - assert app.test_client().get('/').status_code == 500 - assert len(recorded) == 1 + self.assert_equal(app.test_client().get('/').status_code, 500) + self.assert_equal(len(recorded), 1) assert isinstance(recorded[0], ZeroDivisionError) finally: flask.got_request_exception.disconnect(record, app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index e980ff92..20d7a16f 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -24,7 +24,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('context_template.html', value=23) rv = app.test_client().get('/') - assert rv.data == '

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

23|42') def test_original_win(self): app = flask.Flask(__name__) @@ -32,7 +32,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template_string('{{ config }}', config=42) rv = app.test_client().get('/') - assert rv.data == '42' + self.assert_equal(rv.data, '42') def test_standard_context(self): app = flask.Flask(__name__) @@ -48,7 +48,7 @@ class TemplatingTestCase(FlaskTestCase): {{ session.test }} ''') rv = app.test_client().get('/?foo=42') - assert rv.data.split() == ['42', '23', 'False', 'aha'] + self.assert_equal(rv.data.split(), ['42', '23', 'False', 'aha']) def test_escaping(self): text = '

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

Hello World!', '

Hello World!', '

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

Hello World!' - ] + ]) def test_no_escaping(self): app = flask.Flask(__name__) @@ -79,7 +79,7 @@ class TemplatingTestCase(FlaskTestCase): app = flask.Flask(__name__) with app.test_request_context(): macro = flask.get_template_attribute('_macro.html', 'hello') - assert macro('World') == 'Hello World!' + self.assert_equal(macro('World'), 'Hello World!') def test_template_filter(self): app = flask.Flask(__name__) @@ -87,8 +87,8 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] assert 'my_reverse' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['my_reverse'] == my_reverse - assert app.jinja_env.filters['my_reverse']('abcd') == 'dcba' + 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__) @@ -96,8 +96,8 @@ class TemplatingTestCase(FlaskTestCase): def my_reverse(s): return s[::-1] assert 'strrev' in app.jinja_env.filters.keys() - assert app.jinja_env.filters['strrev'] == my_reverse - assert app.jinja_env.filters['strrev']('abcd') == 'dcba' + 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__) @@ -108,7 +108,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - assert rv.data == 'dcba' + self.assert_equal(rv.data, 'dcba') def test_template_filter_with_name_and_template(self): app = flask.Flask(__name__) @@ -119,7 +119,7 @@ class TemplatingTestCase(FlaskTestCase): def index(): return flask.render_template('template_filter.html', value='abcd') rv = app.test_client().get('/') - assert rv.data == 'dcba' + self.assert_equal(rv.data, 'dcba') def test_custom_template_loader(self): class MyFlask(flask.Flask): @@ -132,7 +132,7 @@ class TemplatingTestCase(FlaskTestCase): return flask.render_template('index.html') c = app.test_client() rv = c.get('/') - assert rv.data == 'Hello Custom World!' + self.assert_equal(rv.data, 'Hello Custom World!') def suite(): diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 4e16c257..0cf1980d 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -97,14 +97,14 @@ class TestToolsTestCase(FlaskTestCase): with app.test_client() as c: resp = c.get('/') - assert flask.g.value == 42 - assert resp.data == 'Hello World!' - assert resp.status_code == 200 + self.assert_equal(flask.g.value, 42) + self.assert_equal(resp.data, 'Hello World!') + self.assert_equal(resp.status_code, 200) resp = c.get('/other') assert not hasattr(flask.g, 'value') assert 'Internal Server Error' in resp.data - assert resp.status_code == 500 + self.assert_equal(resp.status_code, 500) flask.g.value = 23 try: From fc2caa4b9c75a6ee8569495008efd1f37c27a8cb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:43:27 +0100 Subject: [PATCH 0123/3143] Changed assert to self.assert_ where it was still in place --- flask/testsuite/basic.py | 80 ++++++++++++++++----------------- flask/testsuite/blueprints.py | 18 ++++---- flask/testsuite/config.py | 20 ++++----- flask/testsuite/deprecations.py | 2 +- flask/testsuite/helpers.py | 34 +++++++------- flask/testsuite/signals.py | 2 +- flask/testsuite/templating.py | 12 ++--- flask/testsuite/testing.py | 4 +- 8 files changed, 86 insertions(+), 86 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 8c2be901..d09ec92e 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -73,7 +73,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - assert not rv.data # head truncates + self.assert_(not rv.data) # head truncates self.assert_equal(c.post('/more').data, 'POST') self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') @@ -97,7 +97,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS']) rv = c.head('/') self.assert_equal(rv.status_code, 200) - assert not rv.data # head truncates + self.assert_(not rv.data) # head truncates self.assert_equal(c.post('/more').data, 'POST') self.assert_equal(c.get('/more').data, 'GET') rv = c.delete('/more') @@ -168,8 +168,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() + 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_and_port(self): app = flask.Flask(__name__) @@ -182,8 +182,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'domain=.example.com' in rv.headers['set-cookie'].lower() - assert 'httponly' in rv.headers['set-cookie'].lower() + self.assert_('domain=.example.com' 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): @@ -205,7 +205,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): flask.session['testing'] = 42 return 'Hello World' rv = app.test_client().get('/', 'http://example.com:8080/') - assert 'path=/bar' in rv.headers['set-cookie'].lower() + self.assert_('path=/bar' in rv.headers['set-cookie'].lower()) def test_missing_session(self): app = flask.Flask(__name__) @@ -213,11 +213,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: f(*args, **kwargs) except RuntimeError, e: - assert e.args and 'session is unavailable' in e.args[0] + self.assert_(e.args and 'session is unavailable' in e.args[0]) else: - assert False, 'expected exception' + self.assert_(False, 'expected exception') with app.test_request_context(): - assert flask.session.get('missing_key') is None + self.assert_(flask.session.get('missing_key') is None) expect_exception(flask.session.__setitem__, 'foo', 42) expect_exception(flask.session.pop, 'foo') @@ -237,7 +237,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): client = app.test_client() rv = client.get('/') - assert 'set-cookie' in rv.headers + self.assert_('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 @@ -250,20 +250,20 @@ class BasicFunctionalityTestCase(FlaskTestCase): permanent = False rv = app.test_client().get('/') - assert 'set-cookie' in rv.headers + self.assert_('set-cookie' in rv.headers) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) - assert match is None + self.assert_(match is None) def test_flashes(self): app = flask.Flask(__name__) app.secret_key = 'testkey' with app.test_request_context(): - assert not flask.session.modified + self.assert_(not flask.session.modified) flask.flash('Zap') flask.session.modified = False flask.flash('Zip') - assert flask.session.modified + self.assert_(flask.session.modified) self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip']) def test_extended_flashing(self): @@ -308,12 +308,12 @@ class BasicFunctionalityTestCase(FlaskTestCase): return response @app.route('/') def index(): - assert 'before' in evts - assert 'after' not in evts + self.assert_('before' in evts) + self.assert_('after' not in evts) return 'request' - assert 'after' not in evts + self.assert_('after' not in evts) rv = app.test_client().get('/').data - assert 'after' in evts + self.assert_('after' in evts) self.assert_equal(rv, 'request|after') def test_teardown_request_handler(self): @@ -328,7 +328,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - assert 'Response' in rv.data + self.assert_('Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_debug_mode(self): @@ -344,7 +344,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): return "Response" rv = app.test_client().get('/') self.assert_equal(rv.status_code, 200) - assert 'Response' in rv.data + self.assert_('Response' in rv.data) self.assert_equal(len(called), 1) def test_teardown_request_handler_error(self): @@ -377,7 +377,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): 1/0 rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - assert 'Internal Server Error' in rv.data + self.assert_('Internal Server Error' in rv.data) self.assert_equal(len(called), 2) def test_before_after_request_order(self): @@ -451,7 +451,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): app = flask.Flask(__name__) @app.errorhandler(MyException) def handle_my_exception(e): - assert isinstance(e, MyException) + self.assert_(isinstance(e, MyException)) return '42' @app.route('/') def index(): @@ -474,7 +474,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.get('/fail') except KeyError, e: - assert isinstance(e, BadRequest) + self.assert_(isinstance(e, BadRequest)) else: self.fail('Expected exception') @@ -509,8 +509,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): try: c.post('/fail', data={'foo': 'index.txt'}) except DebugFilesKeyError, e: - assert 'no file contents were transmitted' in str(e) - assert 'This was submitted: "index.txt"' in str(e) + self.assert_('no file contents were transmitted' in str(e)) + self.assert_('This was submitted: "index.txt"' in str(e)) else: self.fail('Expected exception') @@ -572,8 +572,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): pass with app.test_request_context(): self.assert_equal(flask.url_for('hello', name='test x'), '/hello/test%20x') - assert flask.url_for('hello', name='test x', _external=True) \ - == 'http://localhost/hello/test%20x' + self.assert_equal(flask.url_for('hello', name='test x', _external=True), + 'http://localhost/hello/test%20x') def test_custom_converters(self): from werkzeug.routing import BaseConverter @@ -597,8 +597,8 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(rv.status_code, 200) self.assert_equal(rv.data.strip(), '

Hello World!

') with app.test_request_context(): - assert flask.url_for('static', filename='index.html') \ - == '/static/index.html' + self.assert_equal(flask.url_for('static', filename='index.html'), + '/static/index.html') def test_none_response(self): app = flask.Flask(__name__) @@ -611,7 +611,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assert_equal(str(e), 'View function did not return a response') pass else: - assert "Expected ValueError" + self.assert_("Expected ValueError") def test_request_locals(self): self.assert_equal(repr(flask.g), '') @@ -641,7 +641,7 @@ class BasicFunctionalityTestCase(FlaskTestCase): with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}): pass except Exception, e: - assert isinstance(e, ValueError) + 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')") @@ -768,11 +768,11 @@ class BasicFunctionalityTestCase(FlaskTestCase): @app.before_request def always_first(): flask.request.form['myfile'] - assert False + self.assert_(False) @app.route('/accept', methods=['POST']) def accept_file(): flask.request.form['myfile'] - assert False + self.assert_(False) @app.errorhandler(413) def catcher(error): return '42' @@ -889,17 +889,17 @@ class ContextTestCase(FlaskTestCase): self.assert_equal(index(), 'Hello World!') with app.test_request_context('/meh'): self.assert_equal(meh(), 'http://localhost/meh') - assert flask._request_ctx_stack.top is None + self.assert_(flask._request_ctx_stack.top is None) def test_context_test(self): app = flask.Flask(__name__) - assert not flask.request - assert not flask.has_request_context() + self.assert_(not flask.request) + self.assert_(not flask.has_request_context()) ctx = app.test_request_context() ctx.push() try: - assert flask.request - assert flask.has_request_context() + self.assert_(flask.request) + self.assert_(flask.has_request_context()) finally: ctx.pop() @@ -918,7 +918,7 @@ class ContextTestCase(FlaskTestCase): except RuntimeError: pass else: - assert 0, 'expected runtime error' + self.assert_(0, 'expected runtime error') class SubdomainTestCase(FlaskTestCase): diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index fdd63fee..88e2be36 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -173,8 +173,8 @@ class ModuleTestCase(FlaskTestCase): self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' + self.assert_equal(flask.url_for('admin.static', filename='test.txt'), + '/admin/static/test.txt') with app.test_request_context(): try: @@ -182,7 +182,7 @@ class ModuleTestCase(FlaskTestCase): except TemplateNotFound, e: self.assert_equal(e.name, 'missing.html') else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') @@ -198,13 +198,13 @@ class ModuleTestCase(FlaskTestCase): except NotFound: pass else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') try: f('../__init__.py') except NotFound: pass else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') # testcase for a security issue that may exist on windows systems import os @@ -217,7 +217,7 @@ class ModuleTestCase(FlaskTestCase): except NotFound: pass else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') finally: os.path = old_path @@ -355,8 +355,8 @@ class BlueprintTestCase(FlaskTestCase): self.assert_equal(rv.data.strip(), '/* nested file */') with app.test_request_context(): - assert flask.url_for('admin.static', filename='test.txt') \ - == '/admin/static/test.txt' + self.assert_equal(flask.url_for('admin.static', filename='test.txt'), + '/admin/static/test.txt') with app.test_request_context(): try: @@ -364,7 +364,7 @@ class BlueprintTestCase(FlaskTestCase): except TemplateNotFound, e: self.assert_equal(e.name, 'missing.html') else: - assert 0, 'expected exception' + self.assert_(0, 'expected exception') with flask.Flask(__name__).test_request_context(): self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested') diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 2b689bc0..b9fa6b48 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -25,7 +25,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') - assert 'ConfigTestCase' not in app.config + self.assert_('ConfigTestCase' not in app.config) def test_config_from_file(self): app = flask.Flask(__name__) @@ -54,13 +54,13 @@ class ConfigTestCase(FlaskTestCase): try: app.config.from_envvar('FOO_SETTINGS') except RuntimeError, e: - assert "'FOO_SETTINGS' is not set" in str(e) + self.assert_("'FOO_SETTINGS' is not set" in str(e)) else: - assert 0, 'expected exception' - assert not app.config.from_envvar('FOO_SETTINGS', silent=True) + self.assert_(0, 'expected exception') + self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) os.environ = {'FOO_SETTINGS': __file__.rsplit('.')[0] + '.py'} - assert app.config.from_envvar('FOO_SETTINGS') + self.assert_(app.config.from_envvar('FOO_SETTINGS')) self.common_object_test(app) finally: os.environ = env @@ -71,12 +71,12 @@ class ConfigTestCase(FlaskTestCase): app.config.from_pyfile('missing.cfg') except IOError, e: msg = str(e) - assert msg.startswith('[Errno 2] Unable to load configuration ' - 'file (No such file or directory):') - assert msg.endswith("missing.cfg'") + self.assert_(msg.startswith('[Errno 2] Unable to load configuration ' + 'file (No such file or directory):')) + self.assert_(msg.endswith("missing.cfg'")) else: - assert 0, 'expected config' - assert not app.config.from_pyfile('missing.cfg', silent=True) + self.assert_(0, 'expected config') + self.assert_(not app.config.from_pyfile('missing.cfg', silent=True)) class InstanceTestCase(FlaskTestCase): diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py index 531f7f82..062f40b0 100644 --- a/flask/testsuite/deprecations.py +++ b/flask/testsuite/deprecations.py @@ -29,7 +29,7 @@ class DeprecationsTestCase(FlaskTestCase): c = app.test_client() self.assert_equal(c.get('/').data, '42') self.assert_equal(len(log), 1) - assert 'init_jinja_globals' in str(log[0]['message']) + self.assert_('init_jinja_globals' in str(log[0]['message'])) def suite(): diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 50494640..4bd05e3d 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -108,7 +108,7 @@ class SendfileTestCase(FlaskTestCase): app = flask.Flask(__name__) with app.test_request_context(): rv = flask.send_file('static/index.html') - assert rv.direct_passthrough + self.assert_(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()) @@ -118,8 +118,8 @@ class SendfileTestCase(FlaskTestCase): app.use_x_sendfile = True with app.test_request_context(): rv = flask.send_file('static/index.html') - assert rv.direct_passthrough - assert 'x-sendfile' in rv.headers + self.assert_(rv.direct_passthrough) + self.assert_('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') @@ -142,7 +142,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') - assert 'x-sendfile' in rv.headers + self.assert_('x-sendfile' in rv.headers) self.assert_equal(rv.headers['x-sendfile'], os.path.join(app.root_path, 'static/index.html')) # mimetypes + etag @@ -170,7 +170,7 @@ class SendfileTestCase(FlaskTestCase): with app.test_request_context(): f = StringIO('Test') rv = flask.send_file(f) - assert 'x-sendfile' not in rv.headers + self.assert_('x-sendfile' not in rv.headers) # etags self.assert_equal(len(captured), 1) @@ -207,10 +207,10 @@ class LoggingTestCase(FlaskTestCase): def test_logger_cache(self): app = flask.Flask(__name__) logger1 = app.logger - assert app.logger is logger1 + self.assert_(app.logger is logger1) self.assert_equal(logger1.name, __name__) app.logger_name = __name__ + '/test_logger_cache' - assert app.logger is not logger1 + self.assert_(app.logger is not logger1) def test_debug_log(self): app = flask.Flask(__name__) @@ -230,10 +230,10 @@ class LoggingTestCase(FlaskTestCase): with catch_stderr() as err: c.get('/') out = err.getvalue() - assert 'WARNING in helpers [' in out - assert os.path.basename(__file__.rsplit('.')[0] + '.py') in out - assert 'the standard library is dead' in out - assert 'this is a debug statement' in out + self.assert_('WARNING in helpers [' in out) + self.assert_(os.path.basename(__file__.rsplit('.')[0] + '.py') in out) + self.assert_('the standard library is dead' in out) + self.assert_('this is a debug statement' in out) with catch_stderr() as err: try: @@ -241,7 +241,7 @@ class LoggingTestCase(FlaskTestCase): except ZeroDivisionError: pass else: - assert False, 'debug log ate the exception' + self.assert_(False, 'debug log ate the exception') def test_exception_logging(self): out = StringIO() @@ -255,13 +255,13 @@ class LoggingTestCase(FlaskTestCase): rv = app.test_client().get('/') self.assert_equal(rv.status_code, 500) - assert 'Internal Server Error' in rv.data + self.assert_('Internal Server Error' in rv.data) err = out.getvalue() - assert 'Exception on / [GET]' in err - assert 'Traceback (most recent call last):' in err - assert '1/0' in err - assert 'ZeroDivisionError:' in err + 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) def test_processor_exceptions(self): app = flask.Flask(__name__) diff --git a/flask/testsuite/signals.py b/flask/testsuite/signals.py index d9054a47..da1a68ca 100644 --- a/flask/testsuite/signals.py +++ b/flask/testsuite/signals.py @@ -91,7 +91,7 @@ class SignalsTestCase(FlaskTestCase): try: self.assert_equal(app.test_client().get('/').status_code, 500) self.assert_equal(len(recorded), 1) - assert isinstance(recorded[0], ZeroDivisionError) + self.assert_(isinstance(recorded[0], ZeroDivisionError)) finally: flask.got_request_exception.disconnect(record, app) diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index 20d7a16f..eadbdcf6 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -70,10 +70,10 @@ class TemplatingTestCase(FlaskTestCase): def test_no_escaping(self): app = flask.Flask(__name__) with app.test_request_context(): - assert flask.render_template_string('{{ foo }}', - foo='') == '' - assert flask.render_template('mail.txt', foo='') \ - == ' Mail' + self.assert_equal(flask.render_template_string('{{ foo }}', + foo=''), '') + self.assert_equal(flask.render_template('mail.txt', foo=''), + ' Mail') def test_macros(self): app = flask.Flask(__name__) @@ -86,7 +86,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter() def my_reverse(s): return s[::-1] - 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') @@ -95,7 +95,7 @@ class TemplatingTestCase(FlaskTestCase): @app.template_filter('strrev') def my_reverse(s): return s[::-1] - 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') diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 0cf1980d..f665e12c 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -102,8 +102,8 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(resp.status_code, 200) resp = c.get('/other') - assert not hasattr(flask.g, 'value') - assert 'Internal Server Error' in resp.data + self.assert_(not hasattr(flask.g, 'value')) + self.assert_('Internal Server Error' in resp.data) self.assert_equal(resp.status_code, 500) flask.g.value = 23 From d5cd4f8d59a4a8e5860c8813b3f5d3f3631e9183 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:48:09 +0100 Subject: [PATCH 0124/3143] Updated manifests --- MANIFEST.in | 5 ++++- setup.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3fef8b5b..f82ed054 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include Makefile CHANGES LICENSE AUTHORS +include Makefile CHANGES LICENSE AUTHORS run-tests.py recursive-include artwork * recursive-include tests * recursive-include examples * @@ -9,5 +9,8 @@ recursive-exclude tests *.pyc recursive-exclude tests *.pyo recursive-exclude examples *.pyc recursive-exclude examples *.pyo +recursive-include flask/testsuite/static * +recursive-include flask/testsuite/templates * +recursive-include flask/testsuite/test_apps * prune docs/_build prune docs/_themes/.git diff --git a/setup.py b/setup.py index a51c3887..db2eb9c7 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,10 @@ setup( description='A microframework based on Werkzeug, Jinja2 ' 'and good intentions', long_description=__doc__, - packages=['flask'], + packages=['flask', 'flask.testsuite'], + package_data={ + 'flask.testsuite': ['test_apps/*', 'static/*', 'templates/*'] + }, zip_safe=False, platforms='any', install_requires=[ From fbd6776e68a12aa7bf7d646ca03d568cedc616f3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:48:33 +0100 Subject: [PATCH 0125/3143] Fixed a bug in the testsuite that caused problems when dots where in directory names --- flask/testsuite/config.py | 4 ++-- flask/testsuite/helpers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index b9fa6b48..2ed726c8 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -29,7 +29,7 @@ class ConfigTestCase(FlaskTestCase): def test_config_from_file(self): app = flask.Flask(__name__) - app.config.from_pyfile(__file__.rsplit('.')[0] + '.py') + app.config.from_pyfile(__file__.rsplit('.', 1)[0] + '.py') self.common_object_test(app) def test_config_from_object(self): @@ -59,7 +59,7 @@ class ConfigTestCase(FlaskTestCase): self.assert_(0, 'expected exception') self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True)) - os.environ = {'FOO_SETTINGS': __file__.rsplit('.')[0] + '.py'} + os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'} self.assert_(app.config.from_envvar('FOO_SETTINGS')) self.common_object_test(app) finally: diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 4bd05e3d..56264f70 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -231,7 +231,7 @@ class LoggingTestCase(FlaskTestCase): c.get('/') out = err.getvalue() self.assert_('WARNING in helpers [' in out) - self.assert_(os.path.basename(__file__.rsplit('.')[0] + '.py') 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) From 0851d956b39634d2cd1618e8c075149d85fb599f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:55:11 +0100 Subject: [PATCH 0126/3143] Updated README --- README | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/README b/README index 5c0eb4f1..7d5ada23 100644 --- a/README +++ b/README @@ -11,24 +11,41 @@ ~ Is it ready? - A preview release is out now, and I'm hoping for some - input about what you want from a microframework and - how it should look like. Consider the API to slightly - improve over time. + 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.6.1. `easy_install` will - install them for you if you do `easy_install Flask==dev`. + 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. ~ Where are the docs? - Go to http://flask.pocoo.org/ for a prebuilt version of - the current documentation. Otherwise build them yourself + 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 From 29b7efa36bd12234a4086cafbe9b8a4099991d08 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 11:59:52 +0100 Subject: [PATCH 0127/3143] Improved the logic in which tests are found --- run-tests.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/run-tests.py b/run-tests.py index 96db7f9a..176094b6 100644 --- a/run-tests.py +++ b/run-tests.py @@ -37,21 +37,21 @@ class BetterLoader(TestLoader): if testname[len(common_prefix):] == name: return testcase - all_results = [] + all_tests = [] for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name): - all_results.append((testcase, testname)) + if testname.endswith('.' + name) or ('.' + name + '.') in testname: + all_tests.append(testcase) - if len(all_results) == 1: - return all_results[0][0] - elif not len(all_results): - error = 'could not find testcase "%s"' % name - else: - error = 'Too many matches: for "%s"\n%s' % \ - (name, '\n'.join(' - ' + n for c, n in all_results)) + if not all_tests: + print >> sys.stderr, 'Error: could not find test case for "%s"' % name + sys.exit(1) - print >> sys.stderr, 'Error: %s' % error - sys.exit(1) + if len(all_tests) == 1: + return all_tests[0] + rv = unittest.TestSuite() + for test in all_tests: + rv.addTest(test) + return rv unittest.main(testLoader=BetterLoader(), defaultTest='suite') From f30b1174b85ae44b4d215045ef3361f2c3b3a367 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:00:26 +0100 Subject: [PATCH 0128/3143] Also support full qualified test names --- run-tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run-tests.py b/run-tests.py index 176094b6..b74e7f71 100644 --- a/run-tests.py +++ b/run-tests.py @@ -39,7 +39,8 @@ class BetterLoader(TestLoader): all_tests = [] for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name) or ('.' + name + '.') in testname: + if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ + testname.startswith(name + '.'): all_tests.append(testcase) if not all_tests: From 5a496885544989ca702e2c9996372a8ebf2cf00d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:02:40 +0100 Subject: [PATCH 0129/3143] Moved loader code into the testsuite and out of the runner --- flask/testsuite/__init__.py | 52 +++++++++++++++++++++++++++++++++ run-tests.py | 57 +------------------------------------ 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 8df7a7fd..ff0224bc 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -20,6 +20,9 @@ from contextlib import contextmanager from werkzeug.utils import import_string, find_modules +common_prefix = __name__ + '.' + + def add_to_path(path): def _samefile(x, y): try: @@ -47,6 +50,25 @@ def iter_suites(): yield mod.suite() +def find_all_tests(): + suites = [suite()] + while suites: + s = suites.pop() + try: + suites.extend(s) + except TypeError: + yield s + + +def find_all_tests_with_name(): + for testcase in find_all_tests(): + yield testcase, '%s.%s.%s' % ( + testcase.__class__.__module__, + testcase.__class__.__name__, + testcase._testMethodName + ) + + @contextmanager def catch_warnings(): """Catch warnings in a with block in a list""" @@ -113,6 +135,36 @@ class FlaskTestCase(unittest.TestCase): return self.assertEqual(x, y) +class BetterLoader(unittest.TestLoader): + + def loadTestsFromName(self, name, module=None): + if name == 'suite': + return suite() + for testcase, testname in find_all_tests_with_name(): + if testname == name: + return testcase + if testname.startswith(common_prefix): + if testname[len(common_prefix):] == name: + return testcase + + all_tests = [] + for testcase, testname in find_all_tests_with_name(): + if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ + testname.startswith(name + '.'): + all_tests.append(testcase) + + if not all_tests: + print >> sys.stderr, 'Error: could not find test case for "%s"' % name + sys.exit(1) + + if len(all_tests) == 1: + return all_tests[0] + rv = unittest.TestSuite() + for test in all_tests: + rv.addTest(test) + return rv + + def suite(): setup_paths() suite = unittest.TestSuite() diff --git a/run-tests.py b/run-tests.py index b74e7f71..7d44febc 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,58 +1,3 @@ -import sys import unittest -from unittest.loader import TestLoader -from flask.testsuite import suite - -common_prefix = suite.__module__ + '.' - - -def find_all_tests(): - suites = [suite()] - while suites: - s = suites.pop() - try: - suites.extend(s) - except TypeError: - yield s - - -def find_all_tests_with_name(): - for testcase in find_all_tests(): - yield testcase, '%s.%s.%s' % ( - testcase.__class__.__module__, - testcase.__class__.__name__, - testcase._testMethodName - ) - - -class BetterLoader(TestLoader): - - def loadTestsFromName(self, name, module=None): - if name == 'suite': - return suite() - for testcase, testname in find_all_tests_with_name(): - if testname == name: - return testcase - if testname.startswith(common_prefix): - if testname[len(common_prefix):] == name: - return testcase - - all_tests = [] - for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ - testname.startswith(name + '.'): - all_tests.append(testcase) - - if not all_tests: - print >> sys.stderr, 'Error: could not find test case for "%s"' % name - sys.exit(1) - - if len(all_tests) == 1: - return all_tests[0] - rv = unittest.TestSuite() - for test in all_tests: - rv.addTest(test) - return rv - - +from flask.testsuite import BetterLoader unittest.main(testLoader=BetterLoader(), defaultTest='suite') From a082a5e0ba81af15653fe56501c8d2530d3621dc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:07:49 +0100 Subject: [PATCH 0130/3143] Cleanup in the test finder --- flask/testsuite/__init__.py | 51 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index ff0224bc..5ebc786e 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -20,9 +20,6 @@ from contextlib import contextmanager from werkzeug.utils import import_string, find_modules -common_prefix = __name__ + '.' - - def add_to_path(path): def _samefile(x, y): try: @@ -50,23 +47,18 @@ def iter_suites(): yield mod.suite() -def find_all_tests(): - suites = [suite()] +def find_all_tests(suite): + suites = [suite] while suites: s = suites.pop() try: suites.extend(s) except TypeError: - yield s - - -def find_all_tests_with_name(): - for testcase in find_all_tests(): - yield testcase, '%s.%s.%s' % ( - testcase.__class__.__module__, - testcase.__class__.__name__, - testcase._testMethodName - ) + yield s, '%s.%s.%s' % ( + s.__class__.__module__, + s.__class__.__name__, + s._testMethodName + ) @contextmanager @@ -111,6 +103,10 @@ def emits_module_deprecation_warning(f): class FlaskTestCase(unittest.TestCase): + """Baseclass for all the tests that Flask uses. Use these methods + for testing instead of the camelcased ones in the baseclass for + consistency. + """ def ensure_clean_request_context(self): # make sure we're not leaking a request context since we are @@ -136,20 +132,27 @@ class FlaskTestCase(unittest.TestCase): class BetterLoader(unittest.TestLoader): + """A nicer loader that solves two problems. First of all we are setting + up tests from different sources and we're doing this programmatically + which breaks the default loading logic so this is required anyways. + Secondly this loader has a nicer interpolation for test names than the + default one so you can just do ``run-tests.py ViewTestCase`` and it + will work. + """ + + def getRootSuite(self): + return suite() def loadTestsFromName(self, name, module=None): + root = self.getRootSuite() if name == 'suite': - return suite() - for testcase, testname in find_all_tests_with_name(): - if testname == name: - return testcase - if testname.startswith(common_prefix): - if testname[len(common_prefix):] == name: - return testcase + return root all_tests = [] - for testcase, testname in find_all_tests_with_name(): - if testname.endswith('.' + name) or ('.' + name + '.') in testname or \ + for testcase, testname in find_all_tests(root): + if testname == name or \ + testname.endswith('.' + name) or \ + ('.' + name + '.') in testname or \ testname.startswith(name + '.'): all_tests.append(testcase) From 5235c3e37e17b3271e3cac4646eaf97fd1cc071a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 12:09:55 +0100 Subject: [PATCH 0131/3143] Make BetterLoader() have a better api :) --- flask/testsuite/__init__.py | 3 +-- run-tests.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 5ebc786e..addbb92b 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -157,8 +157,7 @@ class BetterLoader(unittest.TestLoader): all_tests.append(testcase) if not all_tests: - print >> sys.stderr, 'Error: could not find test case for "%s"' % name - sys.exit(1) + raise LookupError('could not find test case for "%s"' % name) if len(all_tests) == 1: return all_tests[0] diff --git a/run-tests.py b/run-tests.py index 7d44febc..0c8d0bdf 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,3 +1,6 @@ import unittest from flask.testsuite import BetterLoader -unittest.main(testLoader=BetterLoader(), defaultTest='suite') +try: + unittest.main(testLoader=BetterLoader(), defaultTest='suite') +except Exception, e: + print 'Error: %s' % e From c8ec453d860ae4754331170108ef03322f29889b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 13:47:01 +0100 Subject: [PATCH 0132/3143] Require that cookies are enabled in the test client for session transactions --- flask/testing.py | 11 ++++++----- flask/testsuite/testing.py | 12 ++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/flask/testing.py b/flask/testing.py index c4d18ca2..6ce96163 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -53,10 +53,12 @@ class FlaskClient(Client): :meth:`~flask.Flask.test_request_context` which are directly passed through. """ + if self.cookie_jar is None: + raise RuntimeError('Session transactions only make sense ' + 'with cookies enabled.') app = self.application environ_overrides = kwargs.pop('environ_overrides', {}) - if self.cookie_jar is not None: - self.cookie_jar.inject_wsgi(environ_overrides) + self.cookie_jar.inject_wsgi(environ_overrides) outer_reqctx = _request_ctx_stack.top with app.test_request_context(*args, **kwargs) as c: sess = app.open_session(c.request) @@ -80,9 +82,8 @@ class FlaskClient(Client): resp = app.response_class() if not app.session_interface.is_null_session(sess): app.save_session(sess, resp) - if self.cookie_jar is not None: - headers = resp.get_wsgi_headers(c.request.environ) - self.cookie_jar.extract_wsgi(c.request.environ, headers) + headers = resp.get_wsgi_headers(c.request.environ) + self.cookie_jar.extract_wsgi(c.request.environ, headers) def open(self, *args, **kwargs): if self.context_preserved: diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index f665e12c..b7a71b1a 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -84,6 +84,18 @@ class TestToolsTestCase(FlaskTestCase): with c.session_transaction(): self.assert_(req is flask.request._get_current_object()) + def test_session_transaction_needs_cookies(self): + app = flask.Flask(__name__) + app.testing = True + c = app.test_client(use_cookies=False) + try: + with c.session_transaction() as s: + pass + except RuntimeError, e: + self.assert_('cookies' in str(e)) + else: + self.fail('Expected runtime error') + def test_test_client_context_binding(self): app = flask.Flask(__name__) @app.route('/') From d49221bf2eeabbfa4a5be4e537b35bae1eb6d272 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:01:46 +0100 Subject: [PATCH 0133/3143] The test client now properly pops response contexts on __exit__ --- CHANGES | 2 ++ docs/upgrading.rst | 5 +++++ flask/testing.py | 13 +++++++++---- flask/testsuite/testing.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index fa8c00bf..b39a2001 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,8 @@ Relase date to be decided, codename to be chosen. 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 + trigger the execution of the teardown handlers. Version 0.7.3 ------------- diff --git a/docs/upgrading.rst b/docs/upgrading.rst index b318292c..0ba46c13 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -36,6 +36,11 @@ longer have to handle that error to avoid an internal server error showing up for the user. If you were catching this down explicitly in the past as `ValueError` you will need to change this. +Due to a bug in the test client Flask 0.7 did not trigger teardown +handlers when the test client was used in a with statement. This was +since fixed but might require some changes in your testsuites if you +relied on this behavior. + Version 0.7 ----------- diff --git a/flask/testing.py b/flask/testing.py index 6ce96163..612b4d4d 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -86,9 +86,7 @@ class FlaskClient(Client): self.cookie_jar.extract_wsgi(c.request.environ, headers) def open(self, *args, **kwargs): - if self.context_preserved: - _request_ctx_stack.pop() - self.context_preserved = False + self._pop_reqctx_if_necessary() kwargs.setdefault('environ_overrides', {}) \ ['flask._preserve_context'] = self.preserve_context @@ -114,5 +112,12 @@ class FlaskClient(Client): def __exit__(self, exc_type, exc_value, tb): self.preserve_context = False + self._pop_reqctx_if_necessary() + + def _pop_reqctx_if_necessary(self): if self.context_preserved: - _request_ctx_stack.pop() + # we have to use _request_ctx_stack.top.pop instead of + # _request_ctx_stack.pop since we want teardown handlers + # to be executed. + _request_ctx_stack.top.pop() + self.context_preserved = False diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index b7a71b1a..32867c3f 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -126,6 +126,38 @@ class TestToolsTestCase(FlaskTestCase): else: raise AssertionError('some kind of exception expected') + def test_reuse_client(self): + app = flask.Flask(__name__) + c = app.test_client() + + with c: + self.assert_equal(c.get('/').status_code, 404) + + with c: + self.assert_equal(c.get('/').status_code, 404) + + def test_test_client_calls_teardown_handlers(self): + app = flask.Flask(__name__) + called = [] + @app.teardown_request + def remember(error): + called.append(error) + + with app.test_client() as c: + self.assert_equal(called, []) + c.get('/') + self.assert_equal(called, []) + self.assert_equal(called, [None]) + + del called[:] + with app.test_client() as c: + self.assert_equal(called, []) + c.get('/') + self.assert_equal(called, []) + c.get('/') + self.assert_equal(called, [None]) + self.assert_equal(called, [None, None]) + def suite(): suite = unittest.TestSuite() From b256e9f36c6d170e7209a577efcac2503c8e48e3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:05:08 +0100 Subject: [PATCH 0134/3143] make_default_options_response now tries to use Werkzeug 0.7 functionality before falling back. --- flask/app.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/flask/app.py b/flask/app.py index b4ae64a5..667f5225 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1305,17 +1305,18 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 """ - # This would be nicer in Werkzeug 0.7, which however currently - # is not released. Werkzeug 0.7 provides a method called - # allowed_methods() that returns all methods that are valid for - # a given path. - methods = [] - try: - _request_ctx_stack.top.url_adapter.match(method='--') - except MethodNotAllowed, e: - methods = e.valid_methods - except HTTPException, e: - pass + adapter = _request_ctx_stack.top.url_adapter + if hasattr(adapter, 'allowed_methods'): + methods = adapter.allowed_methods() + else: + # fallback for Werkzeug < 0.7 + methods = [] + try: + adapter.match(method='--') + except MethodNotAllowed, e: + methods = e.valid_methods + except HTTPException, e: + pass rv = self.response_class() rv.allow.update(methods) return rv From 67101c8b9331d289b3ae5428127814d0c19d643c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:41:25 +0100 Subject: [PATCH 0135/3143] Fake signals no better follow the blinker api --- flask/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/signals.py b/flask/signals.py index 4eedf68f..984accb7 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -34,7 +34,7 @@ except ImportError: 'not installed.') send = lambda *a, **kw: None connect = disconnect = has_receivers_for = receivers_for = \ - temporarily_connected_to = _fail + temporarily_connected_to = connected_to = _fail del _fail # the namespace for code signals. If you are not flask code, do From 367b254c787c59b208899f1dfee78904c1784c73 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 14:51:28 +0100 Subject: [PATCH 0136/3143] Make sure that there is a test for subdomain matching with ports --- flask/testsuite/basic.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index d09ec92e..947d0073 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -961,6 +961,17 @@ class SubdomainTestCase(FlaskTestCase): rv = c.get('/', 'http://mitsuhiko.localhost/') self.assert_equal(rv.data, 'index for mitsuhiko') + def test_subdomain_matching_with_ports(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'localhost:3000' + @app.route('/', subdomain='') + def index(user): + return 'index for %s' % user + + c = app.test_client() + rv = c.get('/', 'http://mitsuhiko.localhost:3000/') + self.assert_equal(rv.data, 'index for mitsuhiko') + @emits_module_deprecation_warning def test_module_subdomain_support(self): app = flask.Flask(__name__) From e509d25d32965a8afd7ca98d67ee6f5a6af11cc8 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 17:35:47 +0100 Subject: [PATCH 0137/3143] Some more cleanups in how the test runner is invoked --- flask/testsuite/__init__.py | 31 +++++++++++++++++++++++++------ run-tests.py | 8 ++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index addbb92b..3f807251 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -21,6 +21,10 @@ from werkzeug.utils import import_string, find_modules def add_to_path(path): + """Adds an entry to sys.path_info if it's not already there.""" + if not os.path.isdir(path): + raise RuntimeError('Tried to add nonexisting path') + def _samefile(x, y): try: return os.path.samefile(x, y) @@ -35,12 +39,8 @@ def add_to_path(path): sys.path.append(path) -def setup_paths(): - add_to_path(os.path.abspath(os.path.join( - os.path.dirname(__file__), 'test_apps'))) - - def iter_suites(): + """Yields all testsuites.""" for module in find_modules(__name__): mod = import_string(module) if hasattr(mod, 'suite'): @@ -48,6 +48,7 @@ def iter_suites(): def find_all_tests(suite): + """Yields all the tests and their names from a given suite.""" suites = [suite] while suites: s = suites.pop() @@ -167,9 +168,27 @@ class BetterLoader(unittest.TestLoader): return rv +def setup_path(): + add_to_path(os.path.abspath(os.path.join( + os.path.dirname(__file__), 'test_apps'))) + + def suite(): - setup_paths() + """A testsuite that has all the Flask tests. You can use this + function to integrate the Flask tests into your own testsuite + in case you want to test that monkeypatches to Flask do not + break it. + """ + setup_path() suite = unittest.TestSuite() for other_suite in iter_suites(): suite.addTest(other_suite) return suite + + +def main(): + """Runs the testsuite as command line application.""" + try: + unittest.main(testLoader=BetterLoader(), defaultTest='suite') + except Exception, e: + print 'Error: %s' % e diff --git a/run-tests.py b/run-tests.py index 0c8d0bdf..3f9259c4 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,6 +1,2 @@ -import unittest -from flask.testsuite import BetterLoader -try: - unittest.main(testLoader=BetterLoader(), defaultTest='suite') -except Exception, e: - print 'Error: %s' % e +from flask.testsuite import main +main() From c0f42a0978d804237d7bd540f81dcdbc1643ea7f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 18:20:33 +0100 Subject: [PATCH 0138/3143] Added shebang to the test run file --- run-tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/run-tests.py b/run-tests.py index 3f9259c4..4ef8a72d 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,2 +1,3 @@ +#!/usr/bin/env python from flask.testsuite import main main() From 2e4c39199d3c8cdcf459675cba6e04bd30f98120 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 21:30:41 +0200 Subject: [PATCH 0139/3143] Refactored logging of internal server errors. Can now be customized --- flask/app.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/flask/app.py b/flask/app.py index 667f5225..1136469f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1216,14 +1216,24 @@ class Flask(_PackageBoundObject): else: raise e - self.logger.exception('Exception on %s [%s]' % ( - request.path, - request.method - )) + self.log_exception((exc_type, exc_value, tb)) if handler is None: return InternalServerError() return handler(e) + def log_exception(self, exc_info): + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error('Exception on %s [%s]' % ( + request.path, + request.method + ), exc_info=exc_info) + def raise_routing_exception(self, request): """Exceptions that are recording during routing are reraised with this method. During debug we are not reraising redirect requests From dc05722b363b4e77357fb943b2d1ee8abea94cb4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 26 Aug 2011 21:42:17 +0200 Subject: [PATCH 0140/3143] Made the foreword less defensive. --- docs/foreword.rst | 50 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/docs/foreword.rst b/docs/foreword.rst index 616c298b..10b886bf 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -9,27 +9,30 @@ What does "micro" mean? ----------------------- To me, the "micro" in microframework refers not only to the simplicity and -small size of the framework, but also to the typically limited complexity -and size of applications that are written with the framework. Also the -fact that you can have an entire application in a single Python file. To -be approachable and concise, a microframework sacrifices a few features -that may be necessary in larger or more complex applications. +small size of the framework, but also the fact that it does not make much +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 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. +For us however the term “micro†does not mean that the whole implementation +has to fit into a single Python file. -Flask provides some tools to deal with the downsides of this approach but -it might be an issue for larger applications because in theory -modifications on these objects might happen anywhere in the same thread. +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 +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 in subdirectories within the Python source tree of the -application. +application. While this can be changed you usually don't have to. The main reason however why Flask is called a "microframework" is the idea to keep the core simple but extensible. There is no database abstraction @@ -40,22 +43,15 @@ was implemented in Flask itself. There are currently extensions for object relational mappers, form validation, upload handling, various open authentication technologies and more. -However Flask is not much code and it is built on a very solid foundation -and with that it is very easy to adapt for large applications. 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 lage 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`. -A Framework and an Example --------------------------- - -Flask is not only a microframework; it is also an example. Based on -Flask, there will be a series of blog posts that explain how to create a -framework. Flask itself is just one way to implement a framework on top -of existing libraries. Unlike many other microframeworks, Flask does not -try to implement everything on its own; it reuses existing code. - Web Development is Dangerous ---------------------------- From 718ef4d699d1de79117f92ff16feed98c3a1aa11 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:32:28 +0200 Subject: [PATCH 0141/3143] Added an XXX to a comment to not miss removing deprecated code later --- flask/ctx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/ctx.py b/flask/ctx.py index 0943d10a..f4eb2188 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -91,7 +91,7 @@ class RequestContext(object): self.match_request() - # Support for deprecated functionality. This is doing away with + # XXX: Support for deprecated functionality. This is doing away with # Flask 1.0 blueprint = self.request.blueprint if blueprint is not None: From 08bf538fb4d173fffcda1397e8485b069c03e718 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:36:53 +0200 Subject: [PATCH 0142/3143] Added a note on the behaviour of the routing system --- docs/design.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/design.rst b/docs/design.rst index 1f391b8c..6ca363a6 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -79,6 +79,22 @@ Furthermore this design makes it possible to use a factory function to create the application which is very helpful for unittesting and similar things (:ref:`app-factories`). +The Routing System +------------------ + +Flask uses the Werkzeug routing system which has 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 +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. + + One Template Engine ------------------- From bb1567dae48fed50f9742b9136c8bd1b82b7bd55 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:42:06 +0200 Subject: [PATCH 0143/3143] Explained why os.getcwd is used for path finding --- flask/helpers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/helpers.py b/flask/helpers.py index d8f7ac63..8f2dccf4 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -484,6 +484,8 @@ def get_root_path(import_name): 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 return os.getcwd() @@ -499,6 +501,7 @@ def find_package(import_name): root_mod = sys.modules[import_name.split('.')[0]] package_path = getattr(root_mod, '__file__', None) if package_path is None: + # support for the interactive python shell package_path = os.getcwd() else: package_path = os.path.abspath(os.path.dirname(package_path)) From 87f50fdc6f01160d75fcc87569f0754b800fc59f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 27 Aug 2011 00:44:46 +0200 Subject: [PATCH 0144/3143] Don't lie to the user about POST redirects --- flask/debughelpers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index b4f73dd3..edf8c111 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -54,7 +54,8 @@ class FormDataRoutingRedirect(AssertionError): buf.append(' Make sure to directly send your %s-request to this URL ' 'since we can\'t make browsers or HTTP clients redirect ' - 'with form data.' % request.method) + 'with form data reliably or without user interaction.' % + request.method) buf.append('\n\nNote: this exception is only raised in debug mode') AssertionError.__init__(self, ''.join(buf).encode('utf-8')) From 23bf2633f67d00418bd31c0c6918c3a99f06dead Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 29 Aug 2011 12:18:25 +0200 Subject: [PATCH 0145/3143] Use the _request_ctx_stack instead of the proxy for consistency with the others. --- flask/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index 1136469f..609ab952 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1397,7 +1397,7 @@ class Flask(_PackageBoundObject): This also triggers the :meth:`url_value_processor` functions before the actualy :meth:`before_request` functions are called. """ - bp = request.blueprint + bp = _request_ctx_stack.top.request.blueprint funcs = self.url_value_preprocessors.get(None, ()) if bp is not None and bp in self.url_value_preprocessors: @@ -1447,7 +1447,7 @@ class Flask(_PackageBoundObject): tighter control over certain resources under testing environments. """ funcs = reversed(self.teardown_request_funcs.get(None, ())) - bp = request.blueprint + 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] From 4dc1796b1c09961098edc0aaac90a28be2b7fc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bartoszkiewicz?= Date: Mon, 29 Aug 2011 21:37:52 +0200 Subject: [PATCH 0146/3143] Fixed session loading in flask.testing.TestClient.session_transaction() --- flask/testing.py | 2 +- flask/testsuite/testing.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/flask/testing.py b/flask/testing.py index 612b4d4d..757e20cc 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -57,7 +57,7 @@ class FlaskClient(Client): raise RuntimeError('Session transactions only make sense ' 'with cookies enabled.') app = self.application - environ_overrides = kwargs.pop('environ_overrides', {}) + environ_overrides = kwargs.setdefault('environ_overrides', {}) self.cookie_jar.inject_wsgi(environ_overrides) outer_reqctx = _request_ctx_stack.top with app.test_request_context(*args, **kwargs) as c: diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 32867c3f..5ee147f9 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -59,6 +59,9 @@ class TestToolsTestCase(FlaskTestCase): self.assert_equal(len(sess), 1) rv = c.get('/') self.assert_equal(rv.data, '[42]') + with c.session_transaction() as sess: + self.assert_equal(len(sess), 1) + self.assert_equal(sess['foo'], [42]) def test_session_transactions_no_null_sessions(self): app = flask.Flask(__name__) From ccf464189b116ea4ee458c2ccb24d64f9272e25b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 30 Aug 2011 14:36:50 +0200 Subject: [PATCH 0147/3143] Added finer control over the session cookie parameters --- CHANGES | 1 + docs/config.rst | 18 +++++++++++++++++- flask/app.py | 6 +++++- flask/sessions.py | 28 ++++++++++++++++++++++++---- flask/testsuite/basic.py | 22 ++++++++++++++++++++++ 5 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index b39a2001..2f47eab1 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Relase date to be decided, codename to be chosen. 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. Version 0.7.3 ------------- diff --git a/docs/config.rst b/docs/config.rst index df31aba0..1ed004d2 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -70,6 +70,20 @@ The following configuration values are used internally by Flask: very risky). ``SECRET_KEY`` the secret key ``SESSION_COOKIE_NAME`` the name of the session cookie +``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If + this is not set, the cookie will be + valid for all subdomains of + ``SERVER_NAME``. +``SESSION_COOKIE_PATH`` the path for the session cookie. If + this is not set the cookie will be valid + for all of ``APPLICATION_ROOT`` or if + that is not set for ``'/'``. +``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set + with the httponly flag. Defaults to + `True`. +``SESSION_COOKIE_SECURE`` controls if the cookie should be set + with the secure flag. Defaults to + `False`. ``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as :class:`datetime.timedelta` object. ``USE_X_SENDFILE`` enable/disable x-sendfile @@ -142,7 +156,9 @@ The following configuration values are used internally by Flask: .. versionadded:: 0.8 ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, - ``APPLICATION_ROOT`` + ``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``, + ``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``, + ``SESSION_COOKIE_SECURE`` Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index 609ab952..3c479df5 100644 --- a/flask/app.py +++ b/flask/app.py @@ -231,12 +231,16 @@ class Flask(_PackageBoundObject): 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, - 'SESSION_COOKIE_NAME': 'session', 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'SERVER_NAME': None, 'APPLICATION_ROOT': None, + 'SESSION_COOKIE_NAME': 'session', + 'SESSION_COOKIE_DOMAIN': None, + 'SESSION_COOKIE_PATH': None, + 'SESSION_COOKIE_HTTPONLY': True, + 'SESSION_COOKIE_SECURE': False, 'MAX_CONTENT_LENGTH': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False diff --git a/flask/sessions.py b/flask/sessions.py index fda84a25..8a9fae51 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -123,16 +123,33 @@ class SessionInterface(object): """Helpful helper method that returns the cookie domain that should be used for the session cookie if session cookies are used. """ + if app.config['SESSION_COOKIE_DOMAIN'] is not None: + 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] def get_cookie_path(self, app): """Returns the path for which the cookie should be valid. The - default implementation uses the value from the ``APPLICATION_ROOT`` - configuration variable or uses ``/`` if it's `None`. + default implementation uses the value from the SESSION_COOKIE_PATH`` + config var if it's set, and falls back to ``APPLICATION_ROOT`` or + uses ``/`` if it's `None`. """ - return app.config['APPLICATION_ROOT'] or '/' + return app.config['SESSION_COOKIE_PATH'] or \ + app.config['APPLICATION_ROOT'] or '/' + + def get_cookie_httponly(self, app): + """Returns True if the session cookie should be httponly. This + currently just returns the value of the ``SESSION_COOKIE_HTTPONLY`` + config var. + """ + return app.config['SESSION_COOKIE_HTTPONLY'] + + def get_cookie_secure(self, app): + """Returns True if the cookie should be secure. This currently + just returns the value of the ``SESSION_COOKIE_SECURE`` setting. + """ + return app.config['SESSION_COOKIE_SECURE'] def get_expiration_time(self, app, session): """A helper method that returns an expiration date for the session @@ -177,9 +194,12 @@ class SecureCookieSessionInterface(SessionInterface): 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=True, domain=domain) + expires=expires, httponly=httponly, + secure=secure, domain=domain) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 947d0073..33975b99 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -207,6 +207,28 @@ class BasicFunctionalityTestCase(FlaskTestCase): rv = app.test_client().get('/', 'http://example.com:8080/') self.assert_('path=/bar' in rv.headers['set-cookie'].lower()) + def test_session_using_session_settings(self): + app = flask.Flask(__name__) + app.config.update( + SECRET_KEY='foo', + SERVER_NAME='www.example.com:8080', + APPLICATION_ROOT='/test', + SESSION_COOKIE_DOMAIN='.example.com', + SESSION_COOKIE_HTTPONLY=False, + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_PATH='/' + ) + @app.route('/') + def index(): + flask.session['testing'] = 42 + 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) + def test_missing_session(self): app = flask.Flask(__name__) def expect_exception(f, *args, **kwargs): From 5f7f3b17dfe2fbeecf82d5745c497b3ea94bf50a Mon Sep 17 00:00:00 2001 From: Christopher Currie Date: Tue, 30 Aug 2011 15:09:56 -0700 Subject: [PATCH 0148/3143] Fix for d5cd4f8d which broke install on Windows --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index db2eb9c7..5dfe3a87 100644 --- a/setup.py +++ b/setup.py @@ -89,9 +89,7 @@ setup( 'and good intentions', long_description=__doc__, packages=['flask', 'flask.testsuite'], - package_data={ - 'flask.testsuite': ['test_apps/*', 'static/*', 'templates/*'] - }, + include_package_data = True, zip_safe=False, platforms='any', install_requires=[ From ee8417dac8364f02524edffddff92329f15b95af Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 1 Sep 2011 16:57:00 +0200 Subject: [PATCH 0149/3143] Late but 2010 -> 2011 in some files --- flask/__init__.py | 2 +- flask/app.py | 2 +- flask/config.py | 2 +- flask/ctx.py | 2 +- flask/globals.py | 2 +- flask/helpers.py | 2 +- flask/logging.py | 2 +- flask/module.py | 2 +- flask/session.py | 2 +- flask/sessions.py | 2 +- flask/signals.py | 2 +- flask/templating.py | 2 +- flask/testing.py | 2 +- flask/wrappers.py | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index 47bf3cab..c1076c33 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -6,7 +6,7 @@ A microframework based on Werkzeug. It's extensively documented and follows best practice patterns. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/app.py b/flask/app.py index 3c479df5..b3978e76 100644 --- a/flask/app.py +++ b/flask/app.py @@ -5,7 +5,7 @@ This module implements the central WSGI application object. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/config.py b/flask/config.py index 06dd02e2..7b6cd1ee 100644 --- a/flask/config.py +++ b/flask/config.py @@ -5,7 +5,7 @@ Implements the configuration related objects. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/ctx.py b/flask/ctx.py index f4eb2188..64293dc5 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -5,7 +5,7 @@ Implements the objects required to keep the context. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/globals.py b/flask/globals.py index 34099263..bcd08722 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -6,7 +6,7 @@ Defines all the global objects that are proxies to the current active context. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/helpers.py b/flask/helpers.py index 8f2dccf4..72c8f170 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -5,7 +5,7 @@ Implements various helpers. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/logging.py b/flask/logging.py index 8379ab66..b992aef8 100644 --- a/flask/logging.py +++ b/flask/logging.py @@ -5,7 +5,7 @@ Implements the logging support for Flask. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/module.py b/flask/module.py index 61b3cbc4..1c4f466c 100644 --- a/flask/module.py +++ b/flask/module.py @@ -5,7 +5,7 @@ Implements a class that represents module blueprints. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/session.py b/flask/session.py index bfe196b0..4d4d2cd6 100644 --- a/flask/session.py +++ b/flask/session.py @@ -6,7 +6,7 @@ This module used to flask with the session global so we moved it over to flask.sessions - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/sessions.py b/flask/sessions.py index 8a9fae51..2795bb1f 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -6,7 +6,7 @@ Implements cookie based sessions based on Werkzeug's secure cookie system. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/signals.py b/flask/signals.py index 984accb7..eeb763d4 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -6,7 +6,7 @@ Implements signals based on blinker if available, otherwise falls silently back to a noop - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ signals_available = False diff --git a/flask/templating.py b/flask/templating.py index d38d3824..90e8772a 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -5,7 +5,7 @@ Implements the bridge to Jinja2. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ import posixpath diff --git a/flask/testing.py b/flask/testing.py index 612b4d4d..474eba15 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -6,7 +6,7 @@ Implements test support helpers. This module is lazily imported and usually not used in production environments. - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ diff --git a/flask/wrappers.py b/flask/wrappers.py index aed0a8d0..f6ec2788 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -5,7 +5,7 @@ Implements the WSGI wrappers (request and response). - :copyright: (c) 2010 by Armin Ronacher. + :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ From 37f9cb9ca69176dec36faff4b2138226b479f65f Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Thu, 1 Sep 2011 12:35:04 -0400 Subject: [PATCH 0150/3143] Import with statement in testsuite, Python 2.5. --- flask/testing.py | 2 ++ flask/testsuite/__init__.py | 3 +++ flask/testsuite/basic.py | 3 +++ flask/testsuite/blueprints.py | 3 +++ flask/testsuite/deprecations.py | 3 +++ flask/testsuite/helpers.py | 3 +++ flask/testsuite/templating.py | 3 +++ flask/testsuite/testing.py | 3 +++ 8 files changed, 23 insertions(+) diff --git a/flask/testing.py b/flask/testing.py index 612b4d4d..67c3b434 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,6 +10,8 @@ :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 diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 3f807251..49b85b23 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -9,6 +9,9 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ + +from __future__ import with_statement + import os import sys import flask diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 078027b9..e71ce75c 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -8,6 +8,9 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ + +from __future__ import with_statement + import re import flask import unittest diff --git a/flask/testsuite/blueprints.py b/flask/testsuite/blueprints.py index 73577961..3f65dd48 100644 --- a/flask/testsuite/blueprints.py +++ b/flask/testsuite/blueprints.py @@ -8,6 +8,9 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ + +from __future__ import with_statement + import flask import unittest import warnings diff --git a/flask/testsuite/deprecations.py b/flask/testsuite/deprecations.py index 062f40b0..795a5d3d 100644 --- a/flask/testsuite/deprecations.py +++ b/flask/testsuite/deprecations.py @@ -8,6 +8,9 @@ :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, catch_warnings diff --git a/flask/testsuite/helpers.py b/flask/testsuite/helpers.py index 56264f70..052d36e1 100644 --- a/flask/testsuite/helpers.py +++ b/flask/testsuite/helpers.py @@ -8,6 +8,9 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ + +from __future__ import with_statement + import os import flask import unittest diff --git a/flask/testsuite/templating.py b/flask/testsuite/templating.py index eadbdcf6..453bfb65 100644 --- a/flask/testsuite/templating.py +++ b/flask/testsuite/templating.py @@ -8,6 +8,9 @@ :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 diff --git a/flask/testsuite/testing.py b/flask/testsuite/testing.py index 32867c3f..2b073fd3 100644 --- a/flask/testsuite/testing.py +++ b/flask/testsuite/testing.py @@ -8,6 +8,9 @@ :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 From 7331ae3df58bdf322122010e598b7e7bb793daea Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 3 Sep 2011 17:13:01 +0200 Subject: [PATCH 0151/3143] Update the testsuite to not freak out about a change in Werkzeug 0.8 that changed the behavior of misconfigured hosts. --- flask/testsuite/basic.py | 56 +++++++++++++--------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index e71ce75c..75286dbf 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -702,63 +702,43 @@ class BasicFunctionalityTestCase(FlaskTestCase): def subdomain(): return 'Foo SubDomain' - try: - rv = app.test_client().get('/') - self.assert_equal(rv.data, 'Foo') - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) + rv = app.test_client().get('/') + self.assert_equal(rv.data, 'Foo') - try: - rv = app.test_client().get('/', 'http://localhost.localdomain:5000') - self.assert_equal(rv.data, 'Foo') - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) + rv = app.test_client().get('/', 'http://localhost.localdomain:5000') + self.assert_equal(rv.data, 'Foo') - try: - rv = app.test_client().get('/', 'https://localhost.localdomain:5000') - self.assert_equal(rv.data, 'Foo') - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) + rv = app.test_client().get('/', 'https://localhost.localdomain:5000') + self.assert_equal(rv.data, 'Foo') - try: - app.config.update(SERVER_NAME='localhost.localdomain') - rv = app.test_client().get('/', 'https://localhost.localdomain') - self.assert_equal(rv.data, 'Foo') - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) + app.config.update(SERVER_NAME='localhost.localdomain') + rv = app.test_client().get('/', 'https://localhost.localdomain') + self.assert_equal(rv.data, 'Foo') try: app.config.update(SERVER_NAME='localhost.localdomain:443') rv = app.test_client().get('/', 'https://localhost.localdomain') - self.assert_equal(rv.data, 'Foo') + # Werkzeug 0.8 + self.assert_equal(rv.status_code, 404) except ValueError, e: + # Werkzeug 0.7 self.assert_equal(str(e), "the server name provided " + "('localhost.localdomain:443') does not match the " + \ "server name from the WSGI environment ('localhost.localdomain')") try: app.config.update(SERVER_NAME='localhost.localdomain') - app.test_client().get('/', 'http://foo.localhost') + rv = app.test_client().get('/', 'http://foo.localhost') + # Werkzeug 0.8 + self.assert_equal(rv.status_code, 404) except ValueError, e: + # Werkzeug 0.7 self.assert_equal(str(e), "the server name provided " + \ "('localhost.localdomain') does not match the " + \ "server name from the WSGI environment ('foo.localhost')") - try: - rv = app.test_client().get('/', 'http://foo.localhost.localdomain') - self.assert_equal(rv.data, 'Foo SubDomain') - except ValueError, e: - raise ValueError( - "No ValueError exception should have been raised \"%s\"" % e - ) + rv = app.test_client().get('/', 'http://foo.localhost.localdomain') + self.assert_equal(rv.data, 'Foo SubDomain') def test_exception_propagation(self): def apprunner(configkey): From b40af3ccd99ba5f392776073fbef9b6b3946e603 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 5 Sep 2011 18:51:47 +0200 Subject: [PATCH 0152/3143] Updated the documentation to show how to set cookies for not yet existing responses --- docs/patterns/deferredcallbacks.rst | 73 +++++++++++++++++++++++++++++ docs/patterns/index.rst | 1 + docs/quickstart.rst | 4 ++ 3 files changed, 78 insertions(+) create mode 100644 docs/patterns/deferredcallbacks.rst diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst new file mode 100644 index 00000000..917c5125 --- /dev/null +++ b/docs/patterns/deferredcallbacks.rst @@ -0,0 +1,73 @@ +.. _deferred-callbacks: + +Deferred Request Callbacks +========================== + +One of the design principles of Flask is that response objects are created +and passed down a chain of potential callbacks that can modify them or +replace them. When the request handling starts, there is no response +object yet. It is created as necessary either by a view function or by +some other component in the system. + +But what happens if you want to modify the response at a point where the +response does not exist yet? A common example for that would be a +before-request function that wants to set a cookie on the response object. + +One way is to avoid the situation. Very often that is possible. For +instance you can try to move that logic into an after-request callback +instead. Sometimes however moving that code there is just not a very +pleasant experience or makes code look very awkward. + +As an alternative possibility you can attach a bunch of callback functions +to the :data:`~flask.g` object and call then at the end of the request. +This way you can defer code execution from anywhere in the application. + + +The Decorator +------------- + +The following decorator is the key. It registers a function on a list on +the :data:`~flask.g` object:: + + from flask import g + + def after_this_request(f): + if not hasattr(g, 'after_request_callbacks'): + g.after_request_callbacks = [] + g.after_request_callbacks.append(f) + return f + + +Calling the Deferred +-------------------- + +Now you can use the `after_this_request` decorator to mark a function to +be called at the end of the request. But we still need to call them. For +this the following function needs to be registered as +:meth:`~flask.Flask.after_request` callback:: + + @app.after_request + def call_after_request_callbacks(response): + for callback in getattr(g, 'after_request_callbacks', ()): + response = callback(response) + return response + + +A Practical Example +------------------- + +Now we can easily at any point in time register a function to be called at +the end of this particular request. For example you can remember the +current language of the user in a cookie in the before-request function:: + + from flask import request + + @app.before_request + def detect_user_language(): + language = request.cookies.get('user_lang') + if language is None: + language = guess_language_from_request() + @after_this_request + def remember_language(response): + response.set_cookie('user_lang', language) + g.language = language diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 3a1e409c..6f63df11 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -36,3 +36,4 @@ Snippet Archives `_. mongokit favicon streaming + cookies diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 690433db..34aa3be4 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -621,6 +621,10 @@ 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. +Sometimes you might want to set a cookie at a point where the response +object does not exist yet. This is possible by utilizing the +:ref:`deferred-callbacks` pattern. + For this also see :ref:`about-responses`. Redirects and Errors From 88617311dba62992bf5736b35e3da9359ac5fedf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 5 Sep 2011 18:52:28 +0200 Subject: [PATCH 0153/3143] Added an testcase for subclassing of Flask to supress logging --- flask/testsuite/subclassing.py | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 flask/testsuite/subclassing.py diff --git a/flask/testsuite/subclassing.py b/flask/testsuite/subclassing.py new file mode 100644 index 00000000..e56ad563 --- /dev/null +++ b/flask/testsuite/subclassing.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.subclassing + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test that certain behavior of flask can be customized by + subclasses. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import flask +import unittest +from StringIO import StringIO +from logging import StreamHandler +from flask.testsuite import FlaskTestCase + + +class FlaskSubclassingTestCase(FlaskTestCase): + + def test_supressed_exception_logging(self): + class SupressedFlask(flask.Flask): + def log_exception(self, exc_info): + pass + + out = StringIO() + app = SupressedFlask(__name__) + app.logger_name = 'flask_tests/test_supressed_exception_logging' + app.logger.addHandler(StreamHandler(out)) + + @app.route('/') + def index(): + 1/0 + + rv = app.test_client().get('/') + self.assert_equal(rv.status_code, 500) + self.assert_('Internal Server Error' in rv.data) + + err = out.getvalue() + self.assert_equal(err, '') + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(FlaskSubclassingTestCase)) + return suite From 7d7d810aea5472101ce04956889dfdb77173a912 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 10 Sep 2011 23:20:58 +0200 Subject: [PATCH 0154/3143] Single quotes for consistency --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 9c0f0211..90dafcc0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -952,7 +952,7 @@ class Flask(_PackageBoundObject): :class:`~werkzeug.routing.Rule` object. """ def decorator(f): - endpoint = options.pop("endpoint", None) + endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator From f6798885e6cc97bccd1e0fd861b54b01fcdb9605 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Tue, 13 Sep 2011 21:22:23 -0400 Subject: [PATCH 0155/3143] Update create_global_jinja_loader docstring, #321. --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 90dafcc0..15f3ead2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -606,7 +606,7 @@ class Flask(_PackageBoundObject): """Creates the loader for the Jinja2 environment. Can be used to override just the loader and keeping the rest unchanged. It's discouraged to override this function. Instead one should override - the :meth:`create_jinja_loader` function instead. + the :meth:`jinja_loader` function instead. The global loader dispatches between the loaders of the application and the individual blueprints. From 17a46a4d239a640049081b5cdcc1ff20ef63e38a Mon Sep 17 00:00:00 2001 From: Joel Perras Date: Wed, 14 Sep 2011 16:53:05 -0300 Subject: [PATCH 0156/3143] Fixed typo in docstring of `dispatch_request` method of `flask.views.View`. --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 2fe34622..60865cd4 100644 --- a/flask/views.py +++ b/flask/views.py @@ -64,7 +64,7 @@ class View(object): def dispatch_request(self): """Subclasses have to override this method to implement the - actual view functionc ode. This method is called with all + actual view function code. This method is called with all the arguments from the URL rule. """ raise NotImplementedError() From f8caa54d31605f8997698d5c6c295ff4cff9ecdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bartoszkiewicz?= Date: Sat, 17 Sep 2011 00:31:09 +0200 Subject: [PATCH 0157/3143] Fixed Blueprint.app_url_value_preprocessor. --- flask/blueprints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/blueprints.py b/flask/blueprints.py index 8bfc610e..ccdda38d 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -292,8 +292,8 @@ class Blueprint(_PackageBoundObject): def app_url_value_preprocessor(self, f): """Same as :meth:`url_value_preprocessor` but application wide. """ - self.record_once(lambda s: s.app.url_value_preprocessor - .setdefault(self.name, []).append(f)) + self.record_once(lambda s: s.app.url_value_preprocessors + .setdefault(None, []).append(f)) return f def app_url_defaults(self, f): From 32c7e43dda6600828ae9b11111d722ab92fb0594 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 17 Sep 2011 19:39:10 +0200 Subject: [PATCH 0158/3143] Simplified HEAD handling for method views --- CHANGES | 2 ++ flask/testsuite/views.py | 35 +++++++++++++++++++++++++++++++++++ flask/views.py | 6 +++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 2f47eab1..0e23b1fb 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,8 @@ Relase date to be decided, codename to be chosen. - 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` + method if no handler was implemented. Version 0.7.3 ------------- diff --git a/flask/testsuite/views.py b/flask/testsuite/views.py index a89c44ac..c7cb0a8a 100644 --- a/flask/testsuite/views.py +++ b/flask/testsuite/views.py @@ -110,6 +110,41 @@ class ViewTestCase(FlaskTestCase): self.assert_equal(rv.headers['X-Parachute'], 'awesome') self.assert_equal(rv.data, 'Awesome') + def test_implicit_head(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + return flask.Response('Blub', headers={ + 'X-Method': flask.request.method + }) + + 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.headers['X-Method'], 'GET') + rv = c.head('/') + self.assert_equal(rv.data, '') + self.assert_equal(rv.headers['X-Method'], 'HEAD') + + def test_explicit_head(self): + app = flask.Flask(__name__) + + class Index(flask.views.MethodView): + def get(self): + return 'GET' + def head(self): + return flask.Response('', headers={'X-Method': 'HEAD'}) + + app.add_url_rule('/', view_func=Index.as_view('index')) + c = app.test_client() + rv = c.get('/') + self.assert_equal(rv.data, 'GET') + rv = c.head('/') + self.assert_equal(rv.data, '') + self.assert_equal(rv.headers['X-Method'], 'HEAD') + def suite(): suite = unittest.TestSuite() diff --git a/flask/views.py b/flask/views.py index 2fe34622..8f368bbc 100644 --- a/flask/views.py +++ b/flask/views.py @@ -143,5 +143,9 @@ class MethodView(View): def dispatch_request(self, *args, **kwargs): meth = getattr(self, request.method.lower(), None) - assert meth is not None, 'Not implemented method' + # if the request method is HEAD and we don't have a handler for it + # retry with GET + if meth is None and request.method == 'HEAD': + meth = getattr(self, 'get', None) + assert meth is not None, 'Not implemented method %r' % request.method return meth(*args, **kwargs) From e68b34eb6c4636f7006a9d4c1172d5d09d6b2dc7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 17 Sep 2011 19:39:19 +0200 Subject: [PATCH 0159/3143] Added API example to the method view docs --- docs/views.rst | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/views.rst b/docs/views.rst index 10ddb57d..441620a6 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -156,3 +156,72 @@ specify a list of decorators to apply in the class declaration:: Due to the implicit self from the caller's perspective you cannot use regular view decorators on the individual methods of the view however, keep this in mind. + +Method Views for APIs +--------------------- + +Web APIs are often working very closely with HTTP verbs so it makes a lot +of sense to implement such an API based on the +:class:`~flask.views.MethodView`. That said, you will notice that the API +will require different URL rules that go to the same method view most of +the time. For instance consider that you are exposing a user object on +the web: + +=============== =============== ====================================== +URL Method Description +--------------- --------------- -------------------------------------- +``/users/`` ``GET`` Gives a list of all users +``/users/`` ``POST`` Creates a new user +``/users/`` ``GET`` Shows a single user +``/users/`` ``PUT`` Updates a single user +``/users/`` ``DELETE`` Deletes a single user +=============== =============== ====================================== + +So how would you go about doing that with the +:class:`~flask.views.MethodView`? The trick is to take advantage of the +fact that you can provide multiple rules to the same view. + +Let's assume for the moment the view would look like this:: + + class UserAPI(MethodView): + + def get(self, user_id): + if user_id is None: + # return a list of users + pass + else: + # expose a single user + pass + + def post(self): + # create a new user + pass + + def delete(self, user_id): + # delete a single user + pass + + def put(self, user_id): + # update a single user + pass + +So how do we hook this up with the routing system? By adding two rules +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']) + app.add_url_rule('/users/', view_func=user_view, + methods=['GET', 'PUT', 'DELETE']) + +If you have a lot of APIs that look similar you can refactor that +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']) + app.add_url_rule('%s<%s:%s>' % (url, pk), view_func=view_func, + methods=['GET', 'PUT', 'DELETE']) + + register_api(UserAPI, 'user_api', '/users/', pk='user_id') From 8f85a3b0d161876de378b35ccac42237a18a5342 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 17 Sep 2011 22:11:57 +0200 Subject: [PATCH 0160/3143] Experimental redirect importing for flask.ext to flask_ and flaskext. --- flask/ext/__init__.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 flask/ext/__init__.py diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py new file mode 100644 index 00000000..2d7ba956 --- /dev/null +++ b/flask/ext/__init__.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +""" + flask.ext + ~~~~~~~~~ + + Redirect imports for extensions. This module basically makes it possible + for us to transition from flaskext.foo to flask_foo without having to + force all extensions to upgrade at the same time. + + When a user does ``from flask.ext.foo import bar`` it will attempt to + imprt ``from flask_foo import bar`` first and when that fails it will + try to import ``from flaskext.foo import bar``. + + We're switching from namespace packages because it was just too painful for + everybody involved. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys + + +class _ExtensionImporter(object): + """This importer redirects imports from this submodule to other + locations. This makes it possible to transition from the old + flaskext.name to the newer flask_name without people having a + hard time. + """ + _module_choices = ['flask_%s', 'flaskext.%s'] + _modules = sys.modules + + def find_module(self, fullname, path=None): + if fullname.rsplit('.', 1)[0] == __name__: + return self + + def load_module(self, fullname): + if fullname in self._modules: + return self._modules[fullname] + packname, modname = fullname.rsplit('.', 1) + for path in self._module_choices: + realname = path % modname + try: + __import__(realname) + except ImportError: + continue + module = self._modules[fullname] = self._modules[realname] + setattr(self._modules[__name__], modname, module) + module.__loader__ = self + return module + raise ImportError(fullname) + + +sys.meta_path.append(_ExtensionImporter()) +del sys, _ExtensionImporter From c72ca16234e4dbea448ea43d002eb500c09f4932 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 02:10:10 +0200 Subject: [PATCH 0161/3143] Added tests for the import hook and fixed a problem with it. --- flask/ext/__init__.py | 40 +++++++----- flask/testsuite/ext.py | 61 +++++++++++++++++++ .../flask_newext_package/__init__.py | 1 + .../flask_newext_package/submodule.py | 2 + .../test_apps/flask_newext_simple.py | 1 + 5 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 flask/testsuite/ext.py create mode 100644 flask/testsuite/test_apps/flask_newext_package/__init__.py create mode 100644 flask/testsuite/test_apps/flask_newext_package/submodule.py create mode 100644 flask/testsuite/test_apps/flask_newext_simple.py diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index 2d7ba956..742e35dd 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -8,7 +8,7 @@ force all extensions to upgrade at the same time. When a user does ``from flask.ext.foo import bar`` it will attempt to - imprt ``from flask_foo import bar`` first and when that fails it will + import ``from flask_foo import bar`` first and when that fails it will try to import ``from flaskext.foo import bar``. We're switching from namespace packages because it was just too painful for @@ -17,38 +17,46 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -import sys class _ExtensionImporter(object): - """This importer redirects imports from this submodule to other - locations. This makes it possible to transition from the old - flaskext.name to the newer flask_name without people having a - hard time. + """This importer redirects imports from this submodule to other locations. + This makes it possible to transition from the old flaskext.name to the + newer flask_name without people having a hard time. """ _module_choices = ['flask_%s', 'flaskext.%s'] - _modules = sys.modules + + def __init__(self): + from sys import meta_path + self.prefix = __name__ + '.' + self.prefix_cutoff = __name__.count('.') + 1 + + def _name(x): + cls = type(x) + return cls.__module__ + '.' + cls.__name__ + this = _name(self) + meta_path[:] = [x for x in meta_path if _name(x) != this] + [self] def find_module(self, fullname, path=None): - if fullname.rsplit('.', 1)[0] == __name__: + if fullname.startswith(self.prefix): return self def load_module(self, fullname): - if fullname in self._modules: - return self._modules[fullname] - packname, modname = fullname.rsplit('.', 1) + from sys import modules + if fullname in modules: + return modules[fullname] + modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] for path in self._module_choices: realname = path % modname try: __import__(realname) except ImportError: continue - module = self._modules[fullname] = self._modules[realname] - setattr(self._modules[__name__], modname, module) - module.__loader__ = self + module = modules[fullname] = modules[realname] + setattr(modules[__name__], modname, module) return module raise ImportError(fullname) -sys.meta_path.append(_ExtensionImporter()) -del sys, _ExtensionImporter +_ExtensionImporter() +del _ExtensionImporter diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py new file mode 100644 index 00000000..ad8d0d16 --- /dev/null +++ b/flask/testsuite/ext.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" + flask.testsuite.ext + ~~~~~~~~~~~~~~~~~~~ + + Tests the extension import thing. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys +import unittest +from flask.testsuite import FlaskTestCase + + +class ExtImportHookTestCase(FlaskTestCase): + + def setup(self): + for entry, value in sys.modules.items(): + if entry.startswith('flask.ext.') and value is not None: + sys.modules.pop(entry, None) + from flask import ext + reload(ext) + + # reloading must not add more hooks + import_hooks = 0 + for item in sys.meta_path: + cls = type(item) + if cls.__module__ == 'flask.ext' and \ + cls.__name__ == '_ExtensionImporter': + import_hooks += 1 + self.assert_equal(import_hooks, 1) + + def test_flaskext_simple_import_normal(self): + from flask.ext.newext_simple import ext_id + self.assert_equal(ext_id, 'newext_simple') + + def test_flaskext_simple_import_module(self): + from flask.ext import newext_simple + self.assert_equal(newext_simple.ext_id, 'newext_simple') + self.assert_equal(newext_simple.__name__, 'flask_newext_simple') + + def test_flaskext_package_import_normal(self): + from flask.ext.newext_package import ext_id + self.assert_equal(ext_id, 'newext_package') + + def test_flaskext_package_import_module(self): + from flask.ext import newext_package + self.assert_equal(newext_package.ext_id, 'newext_package') + self.assert_equal(newext_package.__name__, 'flask_newext_package') + + def test_flaskext_package_import_submodule(self): + from flask.ext.newext_package import submodule + self.assert_equal(submodule.__name__, 'flask_newext_package.submodule') + self.assert_equal(submodule.test_function(), 42) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ExtImportHookTestCase)) + return suite diff --git a/flask/testsuite/test_apps/flask_newext_package/__init__.py b/flask/testsuite/test_apps/flask_newext_package/__init__.py new file mode 100644 index 00000000..3fd13e17 --- /dev/null +++ b/flask/testsuite/test_apps/flask_newext_package/__init__.py @@ -0,0 +1 @@ +ext_id = 'newext_package' diff --git a/flask/testsuite/test_apps/flask_newext_package/submodule.py b/flask/testsuite/test_apps/flask_newext_package/submodule.py new file mode 100644 index 00000000..26ad56b7 --- /dev/null +++ b/flask/testsuite/test_apps/flask_newext_package/submodule.py @@ -0,0 +1,2 @@ +def test_function(): + return 42 diff --git a/flask/testsuite/test_apps/flask_newext_simple.py b/flask/testsuite/test_apps/flask_newext_simple.py new file mode 100644 index 00000000..dc4a3628 --- /dev/null +++ b/flask/testsuite/test_apps/flask_newext_simple.py @@ -0,0 +1 @@ +ext_id = 'newext_simple' From f80bfcaa28da98972002fb906c2559babe75801e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 03:26:46 +0200 Subject: [PATCH 0162/3143] Added tests for old imports --- flask/testsuite/ext.py | 33 ++++++++++++++++--- .../testsuite/test_apps/flaskext/__init__.py | 0 .../flaskext/oldext_package/__init__.py | 1 + .../flaskext/oldext_package/submodule.py | 2 ++ .../test_apps/flaskext/oldext_simple.py | 1 + 5 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 flask/testsuite/test_apps/flaskext/__init__.py create mode 100644 flask/testsuite/test_apps/flaskext/oldext_package/__init__.py create mode 100644 flask/testsuite/test_apps/flaskext/oldext_package/submodule.py create mode 100644 flask/testsuite/test_apps/flaskext/oldext_simple.py diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index ad8d0d16..41bbabeb 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -31,29 +31,52 @@ class ExtImportHookTestCase(FlaskTestCase): import_hooks += 1 self.assert_equal(import_hooks, 1) - def test_flaskext_simple_import_normal(self): + def test_flaskext_new_simple_import_normal(self): from flask.ext.newext_simple import ext_id self.assert_equal(ext_id, 'newext_simple') - def test_flaskext_simple_import_module(self): + def test_flaskext_new_simple_import_module(self): from flask.ext import newext_simple self.assert_equal(newext_simple.ext_id, 'newext_simple') self.assert_equal(newext_simple.__name__, 'flask_newext_simple') - def test_flaskext_package_import_normal(self): + def test_flaskext_new_package_import_normal(self): from flask.ext.newext_package import ext_id self.assert_equal(ext_id, 'newext_package') - def test_flaskext_package_import_module(self): + def test_flaskext_new_package_import_module(self): from flask.ext import newext_package self.assert_equal(newext_package.ext_id, 'newext_package') self.assert_equal(newext_package.__name__, 'flask_newext_package') - def test_flaskext_package_import_submodule(self): + def test_flaskext_new_package_import_submodule(self): from flask.ext.newext_package import submodule self.assert_equal(submodule.__name__, 'flask_newext_package.submodule') self.assert_equal(submodule.test_function(), 42) + def test_flaskext_old_simple_import_normal(self): + from flask.ext.oldext_simple import ext_id + self.assert_equal(ext_id, 'oldext_simple') + + def test_flaskext_old_simple_import_module(self): + from flask.ext import oldext_simple + self.assert_equal(oldext_simple.ext_id, 'oldext_simple') + self.assert_equal(oldext_simple.__name__, 'flaskext.oldext_simple') + + def test_flaskext_old_package_import_normal(self): + from flask.ext.oldext_package import ext_id + self.assert_equal(ext_id, 'oldext_package') + + def test_flaskext_old_package_import_module(self): + from flask.ext import oldext_package + self.assert_equal(oldext_package.ext_id, 'oldext_package') + self.assert_equal(oldext_package.__name__, 'flaskext.oldext_package') + + def test_flaskext_old_package_import_submodule(self): + from flask.ext.oldext_package import submodule + self.assert_equal(submodule.__name__, 'flaskext.oldext_package.submodule') + self.assert_equal(submodule.test_function(), 42) + def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/test_apps/flaskext/__init__.py b/flask/testsuite/test_apps/flaskext/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py b/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py new file mode 100644 index 00000000..7c462065 --- /dev/null +++ b/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py @@ -0,0 +1 @@ +ext_id = 'oldext_package' diff --git a/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py b/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py new file mode 100644 index 00000000..26ad56b7 --- /dev/null +++ b/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py @@ -0,0 +1,2 @@ +def test_function(): + return 42 diff --git a/flask/testsuite/test_apps/flaskext/oldext_simple.py b/flask/testsuite/test_apps/flaskext/oldext_simple.py new file mode 100644 index 00000000..c6664a78 --- /dev/null +++ b/flask/testsuite/test_apps/flaskext/oldext_simple.py @@ -0,0 +1 @@ +ext_id = 'oldext_simple' From e8020e2c5cd6dca816991845bd439e4524594f83 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 12:22:15 +0200 Subject: [PATCH 0163/3143] Added comment to the importer wiping. --- flask/ext/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index 742e35dd..078e7dbd 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -31,6 +31,11 @@ class _ExtensionImporter(object): self.prefix = __name__ + '.' self.prefix_cutoff = __name__.count('.') + 1 + # since people might reload the flask.ext module (by accident or + # intentionally) we have to make sure to not add more than one + # import hook. We can't check class types here either since a new + # class will be created on reload. As a result of that we check + # the name of the class and remove stale instances. def _name(x): cls = type(x) return cls.__module__ + '.' + cls.__name__ From 9df2aefd7f0c3c5d0fc47087da2f51b72cad38fc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 12:22:42 +0200 Subject: [PATCH 0164/3143] Do not set dotted attributes on flask.ext --- flask/ext/__init__.py | 3 ++- flask/testsuite/ext.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index 078e7dbd..14c8d8d4 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -58,7 +58,8 @@ class _ExtensionImporter(object): except ImportError: continue module = modules[fullname] = modules[realname] - setattr(modules[__name__], modname, module) + if '.' not in modname: + setattr(modules[__name__], modname, module) return module raise ImportError(fullname) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 41bbabeb..a64b9bd2 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -31,6 +31,11 @@ class ExtImportHookTestCase(FlaskTestCase): import_hooks += 1 self.assert_equal(import_hooks, 1) + def teardown(self): + from flask import ext + for key in ext.__dict__: + self.assert_('.' not in key) + def test_flaskext_new_simple_import_normal(self): from flask.ext.newext_simple import ext_id self.assert_equal(ext_id, 'newext_simple') From 8840973a337757ba5b7dab0df00a82d8489d3a06 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 12:50:56 +0200 Subject: [PATCH 0165/3143] Fixed a cross reference in the docs. --- docs/patterns/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/index.rst b/docs/patterns/index.rst index 6f63df11..964b1e17 100644 --- a/docs/patterns/index.rst +++ b/docs/patterns/index.rst @@ -36,4 +36,4 @@ Snippet Archives `_. mongokit favicon streaming - cookies + deferredcallbacks From 1e6c5f0975d2ddef4430294c555b1deebb18f93b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 12:51:10 +0200 Subject: [PATCH 0166/3143] Documented flask.ext. --- CHANGES | 1 + docs/api.rst | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGES b/CHANGES index 0e23b1fb..79a669f0 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,7 @@ Relase date to be decided, codename to be chosen. - Added finer control over the session cookie parameters. - HEAD requests to a method view now automatically dispatch to the `get` method if no handler was implemented. +- Implemented the virtual :mod:`flask.ext` package to import extensions from. Version 0.7.3 ------------- diff --git a/docs/api.rst b/docs/api.rst index 1dc25eb1..aca7fda5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -341,6 +341,23 @@ Configuration .. autoclass:: Config :members: +Extensions +---------- + +.. data:: flask.ext + + This module acts as redirect import module to Flask extensions. It was + added in 0.8 as the canonical way to import Flask extensions and makes + it possible for us to have more flexibility in how we distribute + extensions. + + If you want to use an extension named “Flask-Foo†you would import it + from :data:`~flask.ext` as follows:: + + from flask.ext import foo + + .. versionadded:: 0.8 + Useful Internals ---------------- From 0719ad5f4fa7d52d38706b3ee60ef91dcfbaa98d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 13:03:05 +0200 Subject: [PATCH 0167/3143] Documented new import system for extensions --- docs/extensiondev.rst | 80 +++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index 6b407b34..ee0d5e60 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -16,10 +16,10 @@ extension to behave. Anatomy of an Extension ----------------------- -Extensions are all located in a package called ``flaskext.something`` +Extensions are all located in a package called ``flask_something`` where "something" is the name of the library you want to bridge. So for example if you plan to add support for a library named `simplexml` to -Flask, you would name your extension's package ``flaskext.simplexml``. +Flask, you would name your extension's package ``flask_simplexml``. The name of the actual extension (the human readable name) however would be something like "Flask-SimpleXML". Make sure to include the name @@ -27,9 +27,11 @@ be something like "Flask-SimpleXML". Make sure to include the name This is how users can then register dependencies to your extension in their `setup.py` files. -The magic that makes it possible to have your library in a package called -``flaskext.something`` is called a "namespace package". Check out the -guide below how to create something like that. +Flask sets up a redirect package called :data:`flask.ext` where users +should import the extensions from. If you for instance have a package +called ``flask_something`` users would import it as +``flask.ext.something``. This is done to transition from the old +namespace packages. See :ref:`ext-import-transition` for more details. But how do extensions look like themselves? An extension has to ensure that it works with multiple Flask application instances at once. This is @@ -54,35 +56,15 @@ reviewed upfront if they behave as required. So let's get started with creating such a Flask extension. The extension we want to create here will provide very basic support for SQLite3. -There is a script on github called `Flask Extension Wizard`_ which helps -you create the initial folder structure. But for this very basic example -we want to create all by hand to get a better feeling for it. - First we create the following folder structure:: flask-sqlite3/ - flaskext/ - __init__.py - sqlite3.py - setup.py + flask_sqlite3.py LICENSE + README Here's the contents of the most important files: -flaskext/__init__.py -```````````````````` - -The only purpose of this file is to mark the package as namespace package. -This is required so that multiple modules from different PyPI packages can -reside in the same Python package:: - - __import__('pkg_resources').declare_namespace(__name__) - -If you want to know exactly what is happening there, checkout the -distribute or setuptools docs which explain how this works. - -Just make sure to not put anything else in there! - setup.py ```````` @@ -108,9 +90,12 @@ something you can work with:: author_email='your-email@example.com', description='Very short description', long_description=__doc__, - packages=['flaskext'], - namespace_packages=['flaskext'], + py_modules=['flask_sqlite3'], + # if you would be using a package instead use packages instead + # of py_modules: + # packages=['flask_sqlite3'], zip_safe=False, + include_package_data=True, platforms='any', install_requires=[ 'Flask' @@ -127,11 +112,10 @@ something you can work with:: ) That's a lot of code but you can really just copy/paste that from existing -extensions and adapt. This is also what the wizard creates for you if you -use it. +extensions and adapt. -flaskext/sqlite3.py -``````````````````` +flask_sqlite3.py +```````````````` Now this is where your extension code goes. But how exactly should such an extension look like? What are the best practices? Continue reading @@ -170,7 +154,7 @@ manager object that handles opening and closing database connections. The Extension Code ------------------ -Here's the contents of the `flaskext/sqlite3.py` for copy/paste:: +Here's the contents of the `flask_sqlite3.py` for copy/paste:: from __future__ import absolute_import import sqlite3 @@ -223,7 +207,7 @@ So why did we decide on a class based approach here? Because using our extension looks something like this:: from flask import Flask - from flaskext.sqlite3 import SQLite3 + from flask_sqlite3 import SQLite3 app = Flask(__name__) app.config.from_pyfile('the-config.cfg') @@ -343,7 +327,8 @@ Extension Registry`_ and marked appropriately. If you want your own extension to be approved you have to follow these guidelines: 1. An approved Flask extension must provide exactly one package or module - inside the `flaskext` namespace package. + named ``flask_extensionname``. They might also reside inside a + ``flaskext`` namespace packages though this is discouraged now. 2. It must ship a testing suite that can either be invoked with ``make test`` or ``python setup.py test``. For test suites invoked with ``make test`` the extension has to ensure that all dependencies for the test @@ -376,8 +361,27 @@ extension to be approved you have to follow these guidelines: Python 2.7 -.. _Flask Extension Wizard: - http://github.com/mitsuhiko/flask-extension-wizard +.. _ext-import-transition: + +Extension Import Transition +--------------------------- + +For a while we recommended using namespace packages for Flask extensions. +This turned out to be problematic in practice because many different +competing namespace package systems exist and pip would automatically +switch between different systems and this caused a lot of problems for +users. + +Instead we now recommend naming packages ``flask_foo`` instead of the now +deprecated ``flaskext.foo``. Flask 0.8 introduces a redirect import +system that lets uses import from ``flask.ext.foo`` and it will try +``flask_foo`` first and if that fails ``flaskext.foo``. + +Flask extensions should urge users to import from ``flask.ext.foo`` +instead of ``flask_foo`` or ``flaskext_foo`` so that extensions can +transition to the new package name without affecting users. + + .. _OAuth extension: http://packages.python.org/Flask-OAuth/ .. _mailinglist: http://flask.pocoo.org/mailinglist/ .. _IRC channel: http://flask.pocoo.org/community/irc/ From e94416704d70d64b21afb44569039f2b50db966f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 15:03:35 +0200 Subject: [PATCH 0168/3143] Added the flaskext_compat module to support flask.ext in 0.7 and earlier. --- scripts/flaskext_compat.py | 73 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 scripts/flaskext_compat.py diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py new file mode 100644 index 00000000..ddc7e097 --- /dev/null +++ b/scripts/flaskext_compat.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +""" + flaskext_compat + ~~~~~~~~~~~~~~~ + + Implements the ``flask.ext`` virtual package for versions of Flask + older than 0.7. This module is a noop if Flask 0.8 was detected. + + Usage:: + + import flaskext_compat + from flask.ext import foo + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + + +class _ExtensionImporter(object): + """This importer redirects imports from this submodule to other locations. + This makes it possible to transition from the old flaskext.name to the + newer flask_name without people having a hard time. + """ + _module_choices = ['flask_%s', 'flaskext.%s'] + + def __init__(self): + from sys import meta_path + self.prefix = __name__ + '.' + self.prefix_cutoff = __name__.count('.') + 1 + + # since people might reload the flask.ext module (by accident or + # intentionally) we have to make sure to not add more than one + # import hook. We can't check class types here either since a new + # class will be created on reload. As a result of that we check + # the name of the class and remove stale instances. + def _name(x): + cls = type(x) + return cls.__module__ + '.' + cls.__name__ + this = _name(self) + meta_path[:] = [x for x in meta_path if _name(x) != this] + [self] + + def find_module(self, fullname, path=None): + if fullname.startswith(self.prefix): + return self + + def load_module(self, fullname): + from sys import modules + if fullname in modules: + return modules[fullname] + modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] + for path in self._module_choices: + realname = path % modname + try: + __import__(realname) + except ImportError: + continue + module = modules[fullname] = modules[realname] + if '.' not in modname: + setattr(modules[__name__], modname, module) + return module + raise ImportError(fullname) + + +import sys +import flask +try: + __import__('flask.ext') +except ImportError: + sys.modules['flask.ext'] = flask.ext = sys.modules[__name__] + __name__ = __package__ = 'flask.ext' + __path__ = [] + _ExtensionImporter() +del _ExtensionImporter, sys, flask From 933d203828164ef4eabcce29667207deb2409569 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 15:10:50 +0200 Subject: [PATCH 0169/3143] Started extensions introduction document --- docs/contents.rst.inc | 1 + docs/extensions.rst | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 docs/extensions.rst diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index 7a8ebb12..a8ebc0d7 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -20,6 +20,7 @@ instructions for web development with Flask. views reqcontext blueprints + extensions shell patterns/index deploying/index diff --git a/docs/extensions.rst b/docs/extensions.rst new file mode 100644 index 00000000..9d00468a --- /dev/null +++ b/docs/extensions.rst @@ -0,0 +1,48 @@ +Flask Extensions +================ + +Flask extensions extend the functionality of Flask in various different +ways. For instance they add support for databases and other common tasks. + +Finding Extensions +------------------ + +Flask extensions are listed on the `Flask Extension Registry`_ and can be +downloaded with ``easy_install`` or ``pip``. If you add a Flask extension +as dependency to your ``requirements.rst`` or ``setup.py`` file they are +usually installed with a simple command or when your application installs. + +Using Extensions +---------------- + +Extensions typically have documentation that goes along that shows how to +use it. There are no general rules in how extensions are supposed to +behave but they are imported from common locations. If you have an +extension called ``Flask-Foo`` or ``Foo-Flask`` it will be always +importable from ``flask.ext.foo``:: + + from flask.ext import foo + +Flask < 0.8 +----------- + +If you are using Flask 0.7 or earlier the :data:`flask.ext` package will not +exist, instead you have to import from ``flaskext.foo`` or ``flask_foo`` +depending on how the extension is distributed. + +We recommend importing from ``flask.ext`` even with older versions of +Flask however. If you have an application that needs to work with +versions of Flask older than 0.8 you should activate the +``flaskext_compat`` module which provides the ``flask.ext`` module. You +can download it from github: `flaskext_compat.py`_ + +You can use it like this:: + + import flaskext_compat + from flask.ext import foo + +Once the ``flaskext_compat`` module is imported the :data:`flask.ext` will +exist and you can start importing from there. + +.. _Flask Extension Registry: http://flask.pocoo.org/extensions/ +.. _flaskext_compat.py: https://github.com/mitsuhiko/flask/raw/master/scripts/flaskext_compat.py From 9c82840f5256e8aca82c26ab4025712e5c928977 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 18 Sep 2011 18:16:50 +0200 Subject: [PATCH 0170/3143] Added more tests for the import hook --- flask/testsuite/ext.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index a64b9bd2..dc90952f 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -17,7 +17,9 @@ class ExtImportHookTestCase(FlaskTestCase): def setup(self): for entry, value in sys.modules.items(): - if entry.startswith('flask.ext.') and value is not None: + if (entry.startswith('flask.ext.') or + entry.startswith('flask_') or + entry.startswith('flaskext.')) and value is not None: sys.modules.pop(entry, None) from flask import ext reload(ext) @@ -54,6 +56,10 @@ class ExtImportHookTestCase(FlaskTestCase): self.assert_equal(newext_package.ext_id, 'newext_package') self.assert_equal(newext_package.__name__, 'flask_newext_package') + def test_flaskext_new_package_import_submodule_function(self): + from flask.ext.newext_package.submodule import test_function + self.assert_equal(test_function(), 42) + def test_flaskext_new_package_import_submodule(self): from flask.ext.newext_package import submodule self.assert_equal(submodule.__name__, 'flask_newext_package.submodule') @@ -82,6 +88,10 @@ class ExtImportHookTestCase(FlaskTestCase): self.assert_equal(submodule.__name__, 'flaskext.oldext_package.submodule') self.assert_equal(submodule.test_function(), 42) + def test_flaskext_old_package_import_submodule_function(self): + from flask.ext.oldext_package.submodule import test_function + self.assert_equal(test_function(), 42) + def suite(): suite = unittest.TestSuite() From d4d6cc8401b94ab7c92b70ca5e5b21d22636dd5d Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Sun, 18 Sep 2011 13:05:15 +0300 Subject: [PATCH 0171/3143] Clean up auditing command --- setup.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 5dfe3a87..255f1e77 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ class run_audit(Command): user_options = [] def initialize_options(self): - all = None + pass def finalize_options(self): pass @@ -62,22 +62,19 @@ class run_audit(Command): print "Audit requires PyFlakes installed in your system.""" sys.exit(-1) - dirs = ['flask', 'tests'] - # Add example directories - for dir in ['flaskr', 'jqueryexample', 'minitwit']: - dirs.append(os.path.join('examples', dir)) - # TODO: Add test subdirectories warns = 0 + # Define top-level directories + dirs = ('flask', 'examples', 'scripts') for dir in dirs: - for filename in os.listdir(dir): - if filename.endswith('.py') and filename != '__init__.py': - warns += flakes.checkPath(os.path.join(dir, filename)) + for root, _, files in os.walk(dir): + for file in files: + 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) else: print ("No problems found in sourcecode.") - setup( name='Flask', version='0.8-dev', From 907c24e6ffc436971d437b0b9122b840db9466d7 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 23 Jul 2011 11:32:59 -0700 Subject: [PATCH 0172/3143] Document the debug param for Flask.run, it is not part of **options given to run_simple. I am not sure bool() is appropriate. --- flask/app.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 15f3ead2..4c8175d1 100644 --- a/flask/app.py +++ b/flask/app.py @@ -657,7 +657,7 @@ class Flask(_PackageBoundObject): # existing views. context.update(orig_ctx) - def run(self, host='127.0.0.1', port=5000, **options): + def run(self, host='127.0.0.1', port=5000, 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. @@ -680,14 +680,15 @@ class Flask(_PackageBoundObject): :param host: the hostname to listen on. set this to ``'0.0.0.0'`` to have the server available externally as well. :param port: the port of the webserver + :param debug: if given, enable or disable debug mode. :param options: the options to be forwarded to the underlying Werkzeug server. See :func:`werkzeug.serving.run_simple` for more information. """ from werkzeug.serving import run_simple - if 'debug' in options: - self.debug = options.pop('debug') + if debug is not None: + self.debug = bool(debug) options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) try: From 90884a78fbba4068fa93f87626baccd148cd4a57 Mon Sep 17 00:00:00 2001 From: Ron DuPlain Date: Sun, 18 Sep 2011 15:34:37 -0400 Subject: [PATCH 0173/3143] Cross-reference debug docs in run docstring. --- flask/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flask/app.py b/flask/app.py index 4c8175d1..1ad669e3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -681,6 +681,7 @@ class Flask(_PackageBoundObject): to have the server available externally as well. :param port: the port of the webserver :param debug: if given, enable or disable debug mode. + See :attr:`debug`. :param options: the options to be forwarded to the underlying Werkzeug server. See :func:`werkzeug.serving.run_simple` for more From 7997bae5bb048e4623647c57faae447b28da907f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Sep 2011 21:00:28 +0200 Subject: [PATCH 0174/3143] Explicitly activate compat module --- docs/extensions.rst | 8 ++++-- scripts/flaskext_compat.py | 56 +++++++++++++++----------------------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/docs/extensions.rst b/docs/extensions.rst index 9d00468a..5ae80457 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -33,12 +33,14 @@ depending on how the extension is distributed. We recommend importing from ``flask.ext`` even with older versions of Flask however. If you have an application that needs to work with versions of Flask older than 0.8 you should activate the -``flaskext_compat`` module which provides the ``flask.ext`` module. You -can download it from github: `flaskext_compat.py`_ +``flaskext_compat`` module which provides the ``flask.ext`` module if +you activate it. You can download it from github: `flaskext_compat.py`_ -You can use it like this:: +And here is how you can use it:: import flaskext_compat + flaskext_compat.activate() + from flask.ext import foo Once the ``flaskext_compat`` module is imported the :data:`flask.ext` will diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index ddc7e097..03601a1a 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -14,39 +14,30 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import sys +import imp + + +ext_module = imp.new_module('flask.ext') +ext_module.__path__ = [] +ext_module.__package__ = ext_module.__name__ class _ExtensionImporter(object): - """This importer redirects imports from this submodule to other locations. - This makes it possible to transition from the old flaskext.name to the - newer flask_name without people having a hard time. + """This importer redirects imports from the flask.ext module to other + locations. """ _module_choices = ['flask_%s', 'flaskext.%s'] - - def __init__(self): - from sys import meta_path - self.prefix = __name__ + '.' - self.prefix_cutoff = __name__.count('.') + 1 - - # since people might reload the flask.ext module (by accident or - # intentionally) we have to make sure to not add more than one - # import hook. We can't check class types here either since a new - # class will be created on reload. As a result of that we check - # the name of the class and remove stale instances. - def _name(x): - cls = type(x) - return cls.__module__ + '.' + cls.__name__ - this = _name(self) - meta_path[:] = [x for x in meta_path if _name(x) != this] + [self] + prefix = ext_module.__name__ + '.' + prefix_cutoff = prefix.count('.') def find_module(self, fullname, path=None): if fullname.startswith(self.prefix): return self def load_module(self, fullname): - from sys import modules - if fullname in modules: - return modules[fullname] + if fullname in sys.modules: + return sys.modules[fullname] modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] for path in self._module_choices: realname = path % modname @@ -54,20 +45,17 @@ class _ExtensionImporter(object): __import__(realname) except ImportError: continue - module = modules[fullname] = modules[realname] + module = sys.modules[fullname] = sys.modules[realname] if '.' not in modname: - setattr(modules[__name__], modname, module) + setattr(ext_module, modname, module) return module raise ImportError(fullname) -import sys -import flask -try: - __import__('flask.ext') -except ImportError: - sys.modules['flask.ext'] = flask.ext = sys.modules[__name__] - __name__ = __package__ = 'flask.ext' - __path__ = [] - _ExtensionImporter() -del _ExtensionImporter, sys, flask +def activate(): + """Activates the compatibility system.""" + import flask + if hasattr(flask, 'ext'): + return + sys.modules['flask.ext'] = flask.ext = ext_module + sys.meta_path.append(_ExtensionImporter()) From 2b8ea40cd63b7db86d73085d27b9a113688d7053 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Sep 2011 21:00:44 +0200 Subject: [PATCH 0175/3143] import -> activate --- docs/extensions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extensions.rst b/docs/extensions.rst index 5ae80457..f9870ea1 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -43,7 +43,7 @@ And here is how you can use it:: from flask.ext import foo -Once the ``flaskext_compat`` module is imported the :data:`flask.ext` will +Once the ``flaskext_compat`` module is activated the :data:`flask.ext` will exist and you can start importing from there. .. _Flask Extension Registry: http://flask.pocoo.org/extensions/ From 54875e93173f96d44a73431a4929059c55aed797 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Sep 2011 00:03:24 +0200 Subject: [PATCH 0176/3143] Direct passthrough is counter productive --- docs/patterns/streaming.rst | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/patterns/streaming.rst b/docs/patterns/streaming.rst index 1998799f..8393b00b 100644 --- a/docs/patterns/streaming.rst +++ b/docs/patterns/streaming.rst @@ -13,10 +13,7 @@ Basic Usage This is a basic view function that generates a lot of CSV data on the fly. The trick is to have an inner function that uses a generator to generate -data and to then invoke that function and pass it to a response object -that has the ``direct_passthrough`` flag set. This flag is used to inform -the system that data is generated on the fly and should be passed through -without buffering:: +data and to then invoke that function and pass it to a response object:: from flask import Response @@ -25,8 +22,7 @@ without buffering:: def generate(): for row in iter_all_rows(): yield ','.join(row) + '\n' - return Response(generate(), direct_passthrough=True, - mimetype='text/csv') + return Response(generate(), mimetype='text/csv') Each ``yield`` expression is directly sent to the browser. Now though that some WSGI middlewares might break streaming, so be careful there in @@ -51,8 +47,7 @@ quite uncommon, but you can easily do it yourself:: @app.route('/my-large-page.html') def render_large_template(): rows = iter_all_rows() - return Response(stream_template('the_template.html', rows=rows), - direct_passthrough=True) + return Response(stream_template('the_template.html', rows=rows)) The trick here is to get the template object from the Jinja2 environment on the application and to call :meth:`~jinja2.Template.stream` instead of From 457371519545711214a82e2dc6ca8907e3263151 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Sep 2011 18:57:37 +0200 Subject: [PATCH 0177/3143] Reworded before 0.8 ext docs. --- docs/extensions.rst | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/extensions.rst b/docs/extensions.rst index f9870ea1..53dca56e 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -23,18 +23,16 @@ importable from ``flask.ext.foo``:: from flask.ext import foo -Flask < 0.8 ------------ +Flask Before 0.8 +---------------- If you are using Flask 0.7 or earlier the :data:`flask.ext` package will not exist, instead you have to import from ``flaskext.foo`` or ``flask_foo`` -depending on how the extension is distributed. - -We recommend importing from ``flask.ext`` even with older versions of -Flask however. If you have an application that needs to work with -versions of Flask older than 0.8 you should activate the -``flaskext_compat`` module which provides the ``flask.ext`` module if -you activate it. You can download it from github: `flaskext_compat.py`_ +depending on how the extension is distributed. If you want to develop an +application that supports Flask 0.7 or earlier you should still import +from the :data:`flask.ext` package. We provide you with a compatibility +module that provides this package for older versions of Flask. You can +download it from github: `flaskext_compat.py`_ And here is how you can use it:: From 9a70e62b8d545c8de7e3728e544cebe289a39254 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 21 Sep 2011 03:47:36 +0200 Subject: [PATCH 0178/3143] No longer swallow important import errors. This implements a simple but elegant method to find out if an import error can be swallowed to try the next module or if the import error is important and must be reraised. --- flask/ext/__init__.py | 20 ++++++++++++++++++-- scripts/flaskext_compat.py | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index 14c8d8d4..d030336f 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -47,7 +47,7 @@ class _ExtensionImporter(object): return self def load_module(self, fullname): - from sys import modules + from sys import modules, exc_info if fullname in modules: return modules[fullname] modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] @@ -56,12 +56,28 @@ class _ExtensionImporter(object): try: __import__(realname) except ImportError: + exc_type, exc_value, tb = exc_info() + if self.is_important_traceback(realname, tb): + raise exc_type, exc_value, tb continue module = modules[fullname] = modules[realname] if '.' not in modname: setattr(modules[__name__], modname, module) return module - raise ImportError(fullname) + raise ImportError('No module named %s' % fullname) + + def is_important_traceback(self, important_module, tb): + """Walks a traceback's frames and checks if any of the frames + originated in the given important module. If that is the case + then we were able to import the module itself but apparently + something went wrong when the module was imported. (Eg: import + of an import failed). + """ + while tb is not None: + if tb.tb_frame.f_globals.get('__name__') == important_module: + return True + tb = tb.tb_next + return False _ExtensionImporter() diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index 03601a1a..f0b1739d 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -44,12 +44,28 @@ class _ExtensionImporter(object): try: __import__(realname) except ImportError: + exc_type, exc_value, tb = sys.exc_info() + if self.is_important_traceback(realname, tb): + raise exc_type, exc_value, tb continue module = sys.modules[fullname] = sys.modules[realname] if '.' not in modname: setattr(ext_module, modname, module) return module - raise ImportError(fullname) + raise ImportError('No module named %s' % fullname) + + def is_important_traceback(self, important_module, tb): + """Walks a traceback's frames and checks if any of the frames + originated in the given important module. If that is the case + then we were able to import the module itself but apparently + something went wrong when the module was imported. (Eg: import + of an import failed). + """ + while tb is not None: + if tb.tb_frame.f_globals.get('__name__') == important_module: + return True + tb = tb.tb_next + return False def activate(): From ee9b4016323ba0f7a773a5e014bc23bcf1a322cd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 21 Sep 2011 13:01:58 +0200 Subject: [PATCH 0179/3143] Added a comment on who the is_important_traceback seems to work. It's only speculation --- flask/ext/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index d030336f..86ad3da5 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -73,6 +73,20 @@ class _ExtensionImporter(object): something went wrong when the module was imported. (Eg: import of an import failed). """ + # Why can we access f_globals' __name__ here and the value is + # not None? I honestly don't know but here is my thinking. + # The module owns a reference to globals and the frame has one. + # Each function only keeps a reference to the globals not do the + # module which normally causes the problem that when the module + # shuts down all globals are set to None. Now however when the + # import system fails Python takes the short way out and does not + # actually properly shut down the module by Noneing the values + # but by just removing the entry from sys.modules. This means + # that the regular reference based cleanup kicks in. + # + # The good thing: At worst we will swallow an exception we should + # not and the error message will be messed up. However I think + # this should be sufficiently reliable. while tb is not None: if tb.tb_frame.f_globals.get('__name__') == important_module: return True From 9691b7f0bf70553e61a88c8c7b6ffcac776b8d26 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 21 Sep 2011 21:34:47 +0200 Subject: [PATCH 0180/3143] Deal with partially setup packages in the redirect hook. --- flask/ext/__init__.py | 11 ++++++++ flask/testsuite/__init__.py | 26 +++++++++++++++++++ flask/testsuite/ext.py | 7 +++++ .../test_apps/flask_broken/__init__.py | 2 ++ flask/testsuite/test_apps/flask_broken/b.py | 0 scripts/flaskext_compat.py | 10 +++---- 6 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 flask/testsuite/test_apps/flask_broken/__init__.py create mode 100644 flask/testsuite/test_apps/flask_broken/b.py diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index 86ad3da5..1eda6df5 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -57,6 +57,17 @@ class _ExtensionImporter(object): __import__(realname) except ImportError: exc_type, exc_value, tb = exc_info() + # since we only establish the entry in sys.modules at the + # very 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 + # fake module name and that import will succeed since this + # one already has a temporary entry in the modules dict. + # Since this one "succeeded" temporarily that second + # invocation now will have created a fullname entry in + # sys.modules which we have to kill. + modules.pop(fullname, None) if self.is_important_traceback(realname, tb): raise exc_type, exc_value, tb continue diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 49b85b23..9f9e0a60 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -134,6 +134,32 @@ class FlaskTestCase(unittest.TestCase): def assert_equal(self, x, y): return self.assertEqual(x, y) + def assert_raises(self, exc_type, callable=None, *args, **kwargs): + catcher = _ExceptionCatcher(self, exc_type) + if callable is None: + return catcher + with catcher: + callable(*args, **kwargs) + + +class _ExceptionCatcher(object): + + def __init__(self, test_case, exc_type): + self.test_case = test_case + self.exc_type = exc_type + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + exception_name = self.exc_type.__name__ + if exc_type is None: + 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 + return True + class BetterLoader(unittest.TestLoader): """A nicer loader that solves two problems. First of all we are setting diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index dc90952f..966db439 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -8,6 +8,8 @@ :copyright: (c) 2011 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from __future__ import with_statement + import sys import unittest from flask.testsuite import FlaskTestCase @@ -92,6 +94,11 @@ class ExtImportHookTestCase(FlaskTestCase): from flask.ext.oldext_package.submodule import test_function self.assert_equal(test_function(), 42) + def test_flaskext_broken_package_no_module_caching(self): + for x in xrange(2): + with self.assert_raises(ImportError): + import flask.ext.broken + def suite(): suite = unittest.TestSuite() diff --git a/flask/testsuite/test_apps/flask_broken/__init__.py b/flask/testsuite/test_apps/flask_broken/__init__.py new file mode 100644 index 00000000..c194c04f --- /dev/null +++ b/flask/testsuite/test_apps/flask_broken/__init__.py @@ -0,0 +1,2 @@ +import flask.ext.broken.b +import missing_module diff --git a/flask/testsuite/test_apps/flask_broken/b.py b/flask/testsuite/test_apps/flask_broken/b.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index f0b1739d..bb3ada03 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -25,7 +25,8 @@ ext_module.__package__ = ext_module.__name__ class _ExtensionImporter(object): """This importer redirects imports from the flask.ext module to other - locations. + locations. For implementation details see the code in Flask 0.8 + that does the same. """ _module_choices = ['flask_%s', 'flaskext.%s'] prefix = ext_module.__name__ + '.' @@ -45,6 +46,7 @@ class _ExtensionImporter(object): __import__(realname) except ImportError: exc_type, exc_value, tb = sys.exc_info() + sys.modules.pop(fullname, None) if self.is_important_traceback(realname, tb): raise exc_type, exc_value, tb continue @@ -55,12 +57,6 @@ class _ExtensionImporter(object): raise ImportError('No module named %s' % fullname) def is_important_traceback(self, important_module, tb): - """Walks a traceback's frames and checks if any of the frames - originated in the given important module. If that is the case - then we were able to import the module itself but apparently - something went wrong when the module was imported. (Eg: import - of an import failed). - """ while tb is not None: if tb.tb_frame.f_globals.get('__name__') == important_module: return True From 0c75be1172000ad4d0b60920ef5a6fb78880a7d2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 22 Sep 2011 14:08:03 +0200 Subject: [PATCH 0181/3143] Whitespace normalize --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 255f1e77..b1424722 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ setup( 'and good intentions', long_description=__doc__, packages=['flask', 'flask.testsuite'], - include_package_data = True, + include_package_data=True, zip_safe=False, platforms='any', install_requires=[ From d4d75701bc1ea02a0e9b7e3e0d1aee777bd59116 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 22 Sep 2011 14:08:52 +0200 Subject: [PATCH 0182/3143] Added ext to the packages --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b1424722..64b24533 100644 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ setup( description='A microframework based on Werkzeug, Jinja2 ' 'and good intentions', long_description=__doc__, - packages=['flask', 'flask.testsuite'], + packages=['flask', 'flask.ext', 'flask.testsuite'], include_package_data=True, zip_safe=False, platforms='any', From 56afafae67ff0db8b9994a4c59d978b56327e886 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 23 Sep 2011 02:04:21 +0200 Subject: [PATCH 0183/3143] Do not break extension tests if tested with installed extensions. --- flask/testsuite/__init__.py | 14 ++++++-------- flask/testsuite/ext.py | 7 ++++++- run-tests.py | 2 ++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 9f9e0a60..7ac7b536 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -24,7 +24,10 @@ from werkzeug.utils import import_string, find_modules def add_to_path(path): - """Adds an entry to sys.path_info if it's not already there.""" + """Adds an entry to sys.path_info if it's not already there. This does + not append it but moves it to the front so that we can be sure it + is loaded. + """ if not os.path.isdir(path): raise RuntimeError('Tried to add nonexisting path') @@ -33,13 +36,8 @@ def add_to_path(path): return os.path.samefile(x, y) except (IOError, OSError): return False - for entry in sys.path: - try: - if os.path.samefile(path, entry): - return - except (OSError, IOError): - pass - sys.path.append(path) + sys.path[:] = [x for x in sys.path if not _samefile(path, x)] + sys.path.insert(0, path) def iter_suites(): diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index 966db439..c621bcf5 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -18,10 +18,15 @@ from flask.testsuite import FlaskTestCase class ExtImportHookTestCase(FlaskTestCase): def setup(self): + # we clear this out for various reasons. The most important one is + # that a real flaskext could be in there which would disable our + # fake package. Secondly we want to make sure that the flaskext + # import hook does not break on reloading. for entry, value in sys.modules.items(): if (entry.startswith('flask.ext.') or entry.startswith('flask_') or - entry.startswith('flaskext.')) and value is not None: + entry.startswith('flaskext.') or + entry == 'flaskext') and value is not None: sys.modules.pop(entry, None) from flask import ext reload(ext) diff --git a/run-tests.py b/run-tests.py index 4ef8a72d..c1345848 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,3 +1,5 @@ #!/usr/bin/env python +import sys, os +sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) from flask.testsuite import main main() From b8ef7360eb80b2018c21384f1f32693ab22595d0 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 23 Sep 2011 10:34:41 +0300 Subject: [PATCH 0184/3143] Add to sys.path, not sys.path_info --- flask/testsuite/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/testsuite/__init__.py b/flask/testsuite/__init__.py index 7ac7b536..76a4d724 100644 --- a/flask/testsuite/__init__.py +++ b/flask/testsuite/__init__.py @@ -24,7 +24,7 @@ from werkzeug.utils import import_string, find_modules def add_to_path(path): - """Adds an entry to sys.path_info if it's not already there. This does + """Adds an entry to sys.path if it's not already there. This does not append it but moves it to the front so that we can be sure it is loaded. """ From d2eefe25e7fec31a5f4254f6a65934bd00ab0392 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 24 Sep 2011 20:25:28 +0200 Subject: [PATCH 0185/3143] app.name shall be __main__ for console apps --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 1ad669e3..adc017d0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -466,7 +466,7 @@ class Flask(_PackageBoundObject): if self.import_name == '__main__': fn = getattr(sys.modules['__main__'], '__file__', None) if fn is None: - return 'unknown' + return '__main__' return os.path.splitext(os.path.basename(fn))[0] return self.import_name From c6316132b173f96a6f5a90ff3b62f706f8a62560 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 24 Sep 2011 20:27:38 +0200 Subject: [PATCH 0186/3143] Context preserving is now part of Flask and not the test client. This fixes #326 --- CHANGES | 3 +++ flask/ctx.py | 35 ++++++++++++++++++++++++++++++++--- flask/globals.py | 1 + flask/testing.py | 29 +++++++++++------------------ flask/testsuite/basic.py | 17 +++++++++++++++++ 5 files changed, 64 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 79a669f0..1801fc26 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,9 @@ Relase date to be decided, codename to be chosen. - HEAD requests to a method view now automatically dispatch to the `get` method if no handler was implemented. - Implemented the virtual :mod:`flask.ext` package to import extensions from. +- The context preservation on exceptions is now an integral component of + Flask itself and no longer of the test client. This cleaned up some + internal logic and lowers the odds of runaway request contexts in unittests. Version 0.7.3 ------------- diff --git a/flask/ctx.py b/flask/ctx.py index 64293dc5..26781dbd 100644 --- a/flask/ctx.py +++ b/flask/ctx.py @@ -89,6 +89,10 @@ class RequestContext(object): self.flashes = None self.session = None + # indicator if the context was preserved. Next time another context + # is pushed the preserved context is popped. + self.preserved = False + self.match_request() # XXX: Support for deprecated functionality. This is doing away with @@ -114,6 +118,18 @@ 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 + # 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 + # 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 + # memory. This is usually only a problem in testsuite since this + # functionality is not active in production environments. + top = _request_ctx_stack.top + if top is not None and top.preserved: + top.pop() + _request_ctx_stack.push(self) # Open the session at the moment that the request context is @@ -128,8 +144,11 @@ class RequestContext(object): also trigger the execution of functions registered by the :meth:`~flask.Flask.teardown_request` decorator. """ + self.preserved = False self.app.do_teardown_request() - _request_ctx_stack.pop() + rv = _request_ctx_stack.pop() + assert rv is self, 'Popped wrong request context. (%r instead of %r)' \ + % (rv, self) def __enter__(self): self.push() @@ -141,6 +160,16 @@ 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 not self.request.environ.get('flask._preserve_context') and \ - (tb is None or not self.app.preserve_context_on_exception): + 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() + + def __repr__(self): + return '<%s \'%s\' [%s] of %s>' % ( + self.__class__.__name__, + self.request.url, + self.request.method, + self.app.name + ) diff --git a/flask/globals.py b/flask/globals.py index bcd08722..16580d16 100644 --- a/flask/globals.py +++ b/flask/globals.py @@ -19,6 +19,7 @@ def _lookup_object(name): raise RuntimeError('working outside of request context') return getattr(top, name) + # context locals _request_ctx_stack = LocalStack() current_app = LocalProxy(partial(_lookup_object, 'app')) diff --git a/flask/testing.py b/flask/testing.py index d2957036..782b40f6 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -37,7 +37,7 @@ class FlaskClient(Client): Basic usage is outlined in the :ref:`testing` chapter. """ - preserve_context = context_preserved = False + preserve_context = False @contextmanager def session_transaction(self, *args, **kwargs): @@ -88,7 +88,6 @@ class FlaskClient(Client): self.cookie_jar.extract_wsgi(c.request.environ, headers) def open(self, *args, **kwargs): - self._pop_reqctx_if_necessary() kwargs.setdefault('environ_overrides', {}) \ ['flask._preserve_context'] = self.preserve_context @@ -97,14 +96,10 @@ class FlaskClient(Client): follow_redirects = kwargs.pop('follow_redirects', False) builder = make_test_environ_builder(self.application, *args, **kwargs) - old = _request_ctx_stack.top - try: - return Client.open(self, builder, - as_tuple=as_tuple, - buffered=buffered, - follow_redirects=follow_redirects) - finally: - self.context_preserved = _request_ctx_stack.top is not old + return Client.open(self, builder, + as_tuple=as_tuple, + buffered=buffered, + follow_redirects=follow_redirects) def __enter__(self): if self.preserve_context: @@ -114,12 +109,10 @@ class FlaskClient(Client): def __exit__(self, exc_type, exc_value, tb): self.preserve_context = False - self._pop_reqctx_if_necessary() - def _pop_reqctx_if_necessary(self): - if self.context_preserved: - # we have to use _request_ctx_stack.top.pop instead of - # _request_ctx_stack.pop since we want teardown handlers - # to be executed. - _request_ctx_stack.top.pop() - self.context_preserved = False + # on exit we want to clean up earlier. Normally the request context + # stays preserved until the next request in the same thread comes + # in. See RequestGlobals.push() for the general behavior. + top = _request_ctx_stack.top + if top is not None and top.preserved: + top.pop() diff --git a/flask/testsuite/basic.py b/flask/testsuite/basic.py index 75286dbf..1733f0a3 100644 --- a/flask/testsuite/basic.py +++ b/flask/testsuite/basic.py @@ -904,6 +904,23 @@ class BasicFunctionalityTestCase(FlaskTestCase): self.assertEqual(c.get('/bar/').data, 'bar') self.assertEqual(c.get('/bar/123').data, '123') + def test_preserve_only_once(self): + app = flask.Flask(__name__) + app.debug = True + + @app.route('/fail') + def fail_func(): + 1/0 + + c = app.test_client() + for x in xrange(3): + with self.assert_raises(ZeroDivisionError): + c.get('/fail') + + self.assert_(flask._request_ctx_stack.top is not None) + flask._request_ctx_stack.pop() + self.assert_(flask._request_ctx_stack.top is None) + class ContextTestCase(FlaskTestCase): From df1dd57045b38ea7dcca44622744ab24e53e399e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 18:49:00 +0200 Subject: [PATCH 0187/3143] Cleaned up url routing common docs. This fixes #279 --- docs/api.rst | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ flask/app.py | 76 +++++++++------------------------------- 2 files changed, 114 insertions(+), 59 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index aca7fda5..5e5082bf 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -478,6 +478,103 @@ Class Based Views .. autoclass:: flask.views.MethodView :members: +.. _url-route-registrations: + +URL Route Registrations +----------------------- + +Generally there are three ways to define rules for the routing system: + +1. You can use the :meth:`flask.Flask.route` decorator. +2. You can use the :meth:`flask.Flask.add_url_rule` function. +3. You can directly access the underlying Werkzeug routing system + which is exposed as :attr:`flask.Flask.url_map`. + +Variables parts in the route can be specified with angular brackets +(``/user/``). By default a variable part in the URL accepts any +string without a slash however a different converter can be specified as +well by using ````. + +Variable parts are passed to the view function as keyword arguments. + +The following converters are possible available: + +=========== =============================================== +`unicode` 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 +=========== =============================================== + +Here some examples:: + + @app.route('/') + def index(): + pass + + @app.route('/') + def show_user(username): + pass + + @app.route('/post/') + def show_post(post_id): + pass + +An important detail to keep in mind is how Flask deals with trailing +slashes. The idea is to keep each URL unique so the following rules +apply: + +1. If a rule ends with a slash and is requested without a slash by the + user, the user is automatically redirected to the same page with a + trailing slash attached. +2. If a rule does not end with a trailing slash and the user request the + page with a trailing slash, a 404 not found is raised. + +This is consistent with how web servers deal with static files. This +also makes it possible to use relative link targets safely. + +You can also define multiple rules for the same function. They have to be +unique however. Defaults can also be specified. Here for example is a +definition for a URL that accepts an optional page:: + + @app.route('/users/', defaults={'page': 1}) + @app.route('/users/page/') + def show_users(page): + pass + +This specifies that ``/users/`` will be the URL for page one and +``/users/page/N`` will be the URL for page `N`. + +Here the parameters that :meth:`~flask.Flask.route` and +:meth:`~flask.Flask.add_url_rule` accept. The only difference is that +with the route parameter the view function is defined with the decorator +instead of the `view_func` parameter. + +=============== ========================================================== +`rule` the URL roule 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. +`view_func` the function to call when serving a request to the + provided endpoint. If this is not provided one can + specify the function later by storing it in the + :attr:`~flask.Flask.view_functions` dictionary with the + endpoint as key. +`defaults` A dictionary with defaults for this rule. See the + example above for how defaults work. +`subdomain` specifies the rule for the subdomain in case subdomain + matching is in use. If not specified the default + subdomain is assumed. +`**options` the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change to + Werkzeug is handling of method options. methods is a list + of methods this rule should be limited to (`GET`, `POST` + etc.). By default a rule just listens for `GET` (and + implicitly `HEAD`). Starting with Flask 0.6, `OPTIONS` is + implicitly added and handled by the standard request + handling. They have to be specified as keyword arguments. +=============== ========================================================== + .. _view-func-options: View Function Options diff --git a/flask/app.py b/flask/app.py index adc017d0..91ea3414 100644 --- a/flask/app.py +++ b/flask/app.py @@ -824,9 +824,11 @@ class Flask(_PackageBoundObject): app.view_functions['index'] = index - If a view function is provided some defaults can be specified directly - on the view function. For more information refer to - :ref:`view-func-options`. + Internally :meth:`route` invokes :meth:`add_url_rule` so if you want + to customize the behavior via subclassing you only need to change + this method. + + For more information refer to :ref:`url-route-registrations`. .. versionchanged:: 0.2 `view_func` parameter added. @@ -885,73 +887,29 @@ class Flask(_PackageBoundObject): def route(self, rule, **options): """A decorator that is used to register a view function for a - given URL rule. Example:: + given URL rule. This does the same thing as :meth:`add_url_rule` + but is intended for decorator usage:: @app.route('/') def index(): return 'Hello World' - Variables parts in the route can be specified with angular - brackets (``/user/``). By default a variable part - in the URL accepts any string without a slash however a different - converter can be specified as well by using ````. - - Variable parts are passed to the view function as keyword - arguments. - - The following converters are possible: - - =========== =========================================== - `int` accepts integers - `float` like `int` but for floating point values - `path` like the default but also accepts slashes - =========== =========================================== - - Here some examples:: - - @app.route('/') - def index(): - pass - - @app.route('/') - def show_user(username): - pass - - @app.route('/post/') - def show_post(post_id): - pass - - An important detail to keep in mind is how Flask deals with trailing - slashes. The idea is to keep each URL unique so the following rules - apply: - - 1. If a rule ends with a slash and is requested without a slash - by the user, the user is automatically redirected to the same - page with a trailing slash attached. - 2. If a rule does not end with a trailing slash and the user request - the page with a trailing slash, a 404 not found is raised. - - This is consistent with how web servers deal with static files. This - also makes it possible to use relative link targets safely. - - The :meth:`route` decorator accepts a couple of other arguments - as well: + For more information refer to :ref:`url-route-registrations`. :param rule: the URL rule as string - :param methods: a list of methods this rule should be limited + :param endpoint: the endpoint for the registered URL rule. Flask + itself assumes the name of the view function as + endpoint + :param view_func: the function to call when serving a request to the + provided endpoint + :param options: the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change + to Werkzeug is handling of method options. methods + is a list of methods this rule should be limited to (`GET`, `POST` etc.). By default a rule just listens for `GET` (and implicitly `HEAD`). Starting with Flask 0.6, `OPTIONS` is implicitly added and handled by the standard request handling. - :param subdomain: specifies the rule for the subdomain in case - subdomain matching is in use. - :param strict_slashes: can be used to disable the strict slashes - setting for this rule. See above. - :param endpoint: Since version 0.8 you can also pass the enpoint, - it will be used instead of generating the endpoint - from the function name. - :param options: other options to be forwarded to the underlying - :class:`~werkzeug.routing.Rule` object. """ def decorator(f): endpoint = options.pop('endpoint', None) From 142c9c391be4ad182fb17d4190a19feea7b79246 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 18:56:15 +0200 Subject: [PATCH 0188/3143] Fixed typos in copy/pasted text --- docs/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 5e5082bf..d80f5221 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -490,14 +490,14 @@ Generally there are three ways to define rules for the routing system: 3. You can directly access the underlying Werkzeug routing system which is exposed as :attr:`flask.Flask.url_map`. -Variables parts in the route can be specified with angular brackets +Variable parts in the route can be specified with angular brackets (``/user/``). By default a variable part in the URL accepts any string without a slash however a different converter can be specified as well by using ````. Variable parts are passed to the view function as keyword arguments. -The following converters are possible available: +The following converters are available: =========== =============================================== `unicode` accepts any text without a slash (the default) From 83b1da8df8ead2d419382af2ab3c3a4fa9db3b8b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 19:01:30 +0200 Subject: [PATCH 0189/3143] Inserted verb --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index d80f5221..59b99295 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -545,7 +545,7 @@ definition for a URL that accepts an optional page:: This specifies that ``/users/`` will be the URL for page one and ``/users/page/N`` will be the URL for page `N`. -Here the parameters that :meth:`~flask.Flask.route` and +Here are the parameters that :meth:`~flask.Flask.route` and :meth:`~flask.Flask.add_url_rule` accept. The only difference is that with the route parameter the view function is defined with the decorator instead of the `view_func` parameter. From 6d0b3264c2263e06eb0d5c854fd5782f04a34b24 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 19:02:47 +0200 Subject: [PATCH 0190/3143] Mention localhost:5000 being pointless in the docs. It already says that down on the page but not on the key --- docs/config.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 1ed004d2..75ddf86d 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -90,7 +90,9 @@ The following configuration values are used internally by Flask: ``LOGGER_NAME`` the name of the logger ``SERVER_NAME`` the name and port number of the server. Required for subdomain support (e.g.: - ``'localhost:5000'``) + ``'myapp.dev:5000'``) Note that + localhost does not support subdomains so + setting this to “localhost†help. ``APPLICATION_ROOT`` If the application does not occupy a whole domain or subdomain this can be set to the path where the application From 6dccf775468ea0a849d3c1c3965e3a1db6a36a3f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 19:12:41 +0200 Subject: [PATCH 0191/3143] PERMANENT_SESSION_LIFETIME can now be an integer. This fixes #310 --- docs/api.rst | 7 +++++++ docs/config.rst | 2 ++ flask/app.py | 35 ++++++++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 59b99295..13fcb659 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -217,6 +217,13 @@ implementation that Flask is using. .. autoclass:: SessionMixin :members: +.. admonition:: Notice + + The ``PERMANENT_SESSION_LIFETIME`` config key can also be an integer + starting with Flask 0.8. Either catch this down yourself or use + the :attr:`~flask.Flask.permanent_session_lifetime` attribute on the + app which converts the result to an integer automatically. + Test Client ----------- diff --git a/docs/config.rst b/docs/config.rst index 75ddf86d..32e267c3 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -86,6 +86,8 @@ The following configuration values are used internally by Flask: `False`. ``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as :class:`datetime.timedelta` object. + Starting with Flask 0.8 this can also be + an integer representing seconds. ``USE_X_SENDFILE`` enable/disable x-sendfile ``LOGGER_NAME`` the name of the logger ``SERVER_NAME`` the name and port number of the server. diff --git a/flask/app.py b/flask/app.py index 91ea3414..83721a6b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -177,15 +177,6 @@ class Flask(_PackageBoundObject): #: `SESSION_COOKIE_NAME` configuration key. Defaults to ``'session'`` session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME') - #: A :class:`~datetime.timedelta` which is used to set the expiration - #: date of a permanent session. The default is 31 days which makes a - #: permanent session survive for roughly one month. - #: - #: This attribute can also be configured from the config with the - #: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to - #: ``timedelta(days=31)`` - permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME') - #: Enable this if you want to use the X-Sendfile feature. Keep in #: mind that the server has to support this. This only affects files #: sent with the :func:`send_file` method. @@ -495,6 +486,32 @@ class Flask(_PackageBoundObject): return rv return self.debug + def _get_permanent_session_lifetime(self): + """A :class:`~datetime.timedelta` which is used to set the expiration + date of a permanent session. The default is 31 days which makes a + permanent session survive for roughly one month. + + This attribute can also be configured from the config with the + `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to + ``timedelta(days=31)``. + + If you want to have this value as seconds you can use ``total_seconds()``:: + + app.permanent_session_lifetime.total_seconds() + + Note that the config key can be a timedelta object or number of seconds + as integer since Flask 0.8. + """ + rv = self.config['PERMANENT_SESSION_LIFETIME'] + if not isinstance(rv, timedelta): + return timedelta(seconds=rv) + return rv + def _set_permanent_session_lifetime(self, value): + self.config['PERMANENT_SESSION_LIFETIME'] = value + permanent_session_lifetime = property(_get_permanent_session_lifetime, + _set_permanent_session_lifetime) + del _get_permanent_session_lifetime, _set_permanent_session_lifetime + @property def logger(self): """A :class:`logging.Logger` object for this application. The From 8da8a21b69cc06e58b1b28fa0ecec63b5cf3d9b4 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 19:17:50 +0200 Subject: [PATCH 0192/3143] Moved the conversion thing into the ConfigAttribute. --- flask/app.py | 42 +++++++++++++++------------------------ flask/config.py | 8 ++++++-- flask/testsuite/config.py | 5 +++++ 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/flask/app.py b/flask/app.py index 83721a6b..ebf4e6a6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -41,6 +41,12 @@ from .signals import request_started, request_finished, got_request_exception, \ _logger_lock = Lock() +def _make_timedelta(value): + if not isinstance(value, timedelta): + return timedelta(seconds=value) + return value + + def setupmethod(f): """Wraps a method so that it performs a check in debug mode if the first request was already handled. @@ -177,6 +183,16 @@ class Flask(_PackageBoundObject): #: `SESSION_COOKIE_NAME` configuration key. Defaults to ``'session'`` session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME') + #: A :class:`~datetime.timedelta` which is used to set the expiration + #: date of a permanent session. The default is 31 days which makes a + #: permanent session survive for roughly one month. + #: + #: This attribute can also be configured from the config with the + #: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to + #: ``timedelta(days=31)`` + permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME', + get_converter=_make_timedelta) + #: Enable this if you want to use the X-Sendfile feature. Keep in #: mind that the server has to support this. This only affects files #: sent with the :func:`send_file` method. @@ -486,32 +502,6 @@ class Flask(_PackageBoundObject): return rv return self.debug - def _get_permanent_session_lifetime(self): - """A :class:`~datetime.timedelta` which is used to set the expiration - date of a permanent session. The default is 31 days which makes a - permanent session survive for roughly one month. - - This attribute can also be configured from the config with the - `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to - ``timedelta(days=31)``. - - If you want to have this value as seconds you can use ``total_seconds()``:: - - app.permanent_session_lifetime.total_seconds() - - Note that the config key can be a timedelta object or number of seconds - as integer since Flask 0.8. - """ - rv = self.config['PERMANENT_SESSION_LIFETIME'] - if not isinstance(rv, timedelta): - return timedelta(seconds=rv) - return rv - def _set_permanent_session_lifetime(self, value): - self.config['PERMANENT_SESSION_LIFETIME'] = value - permanent_session_lifetime = property(_get_permanent_session_lifetime, - _set_permanent_session_lifetime) - del _get_permanent_session_lifetime, _set_permanent_session_lifetime - @property def logger(self): """A :class:`logging.Logger` object for this application. The diff --git a/flask/config.py b/flask/config.py index 7b6cd1ee..67dbf9b7 100644 --- a/flask/config.py +++ b/flask/config.py @@ -21,13 +21,17 @@ from werkzeug.utils import import_string class ConfigAttribute(object): """Makes an attribute forward to the config""" - def __init__(self, name): + def __init__(self, name, get_converter=None): self.__name__ = name + self.get_converter = get_converter def __get__(self, obj, type=None): if obj is None: return self - return obj.config[self.__name__] + rv = obj.config[self.__name__] + if self.get_converter is not None: + rv = self.get_converter(rv) + return rv def __set__(self, obj, value): obj.config[self.__name__] = value diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 2ed726c8..1f6d79c3 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -78,6 +78,11 @@ class ConfigTestCase(FlaskTestCase): self.assert_(0, 'expected config') self.assert_(not app.config.from_pyfile('missing.cfg', silent=True)) + def test_session_lifetime(self): + app = flask.Flask(__name__) + app.config['PERMANENT_SESSION_LIFETIME'] = 42 + self.assert_equal(app.permanent_session_lifetime.total_seconds(), 42) + class InstanceTestCase(FlaskTestCase): From 363d9ed10dfa35816e4b635f9cc761925c8ef2ac Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 19:24:37 +0200 Subject: [PATCH 0193/3143] More doc typo fixes --- docs/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 13fcb659..7695788e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -513,7 +513,7 @@ The following converters are available: `path` like the default but also accepts slashes =========== =============================================== -Here some examples:: +Here are some examples:: @app.route('/') def index(): @@ -534,7 +534,7 @@ apply: 1. If a rule ends with a slash and is requested without a slash by the user, the user is automatically redirected to the same page with a trailing slash attached. -2. If a rule does not end with a trailing slash and the user request the +2. If a rule does not end with a trailing slash and the user requests the page with a trailing slash, a 404 not found is raised. This is consistent with how web servers deal with static files. This From 396c4bdcc0f30241e009516e430bc1742ad344bf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 25 Sep 2011 22:19:20 +0200 Subject: [PATCH 0194/3143] Fixed a typo. "Does not" of course --- docs/config.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 32e267c3..ca724dce 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -94,7 +94,8 @@ The following configuration values are used internally by Flask: Required for subdomain support (e.g.: ``'myapp.dev:5000'``) Note that localhost does not support subdomains so - setting this to “localhost†help. + setting this to “localhost†does not + help. ``APPLICATION_ROOT`` If the application does not occupy a whole domain or subdomain this can be set to the path where the application From f01b654ac4c0a4789369440557267b75df55fd59 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 27 Sep 2011 12:27:33 +0200 Subject: [PATCH 0195/3143] total_seconds -> seconds for 2.6 and earlier --- flask/testsuite/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/testsuite/config.py b/flask/testsuite/config.py index 1f6d79c3..ad1721fd 100644 --- a/flask/testsuite/config.py +++ b/flask/testsuite/config.py @@ -81,7 +81,7 @@ class ConfigTestCase(FlaskTestCase): def test_session_lifetime(self): app = flask.Flask(__name__) app.config['PERMANENT_SESSION_LIFETIME'] = 42 - self.assert_equal(app.permanent_session_lifetime.total_seconds(), 42) + self.assert_equal(app.permanent_session_lifetime.seconds, 42) class InstanceTestCase(FlaskTestCase): From 95c4dcb4d50e69b927855cd6a446579d2fe45b4e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 27 Sep 2011 13:33:23 +0200 Subject: [PATCH 0196/3143] Refactored flask.ext process to not swallow exceptions on weird Pythons. --- flask/ext/__init__.py | 92 ++-------------------------- flask/exthook.py | 119 +++++++++++++++++++++++++++++++++++++ flask/testsuite/ext.py | 16 ++++- scripts/flaskext_compat.py | 93 ++++++++++++++++++++++------- 4 files changed, 211 insertions(+), 109 deletions(-) create mode 100644 flask/exthook.py diff --git a/flask/ext/__init__.py b/flask/ext/__init__.py index 1eda6df5..f29958a1 100644 --- a/flask/ext/__init__.py +++ b/flask/ext/__init__.py @@ -19,91 +19,11 @@ """ -class _ExtensionImporter(object): - """This importer redirects imports from this submodule to other locations. - This makes it possible to transition from the old flaskext.name to the - newer flask_name without people having a hard time. - """ - _module_choices = ['flask_%s', 'flaskext.%s'] - - def __init__(self): - from sys import meta_path - self.prefix = __name__ + '.' - self.prefix_cutoff = __name__.count('.') + 1 - - # since people might reload the flask.ext module (by accident or - # intentionally) we have to make sure to not add more than one - # import hook. We can't check class types here either since a new - # class will be created on reload. As a result of that we check - # the name of the class and remove stale instances. - def _name(x): - cls = type(x) - return cls.__module__ + '.' + cls.__name__ - this = _name(self) - meta_path[:] = [x for x in meta_path if _name(x) != this] + [self] - - def find_module(self, fullname, path=None): - if fullname.startswith(self.prefix): - return self - - def load_module(self, fullname): - from sys import modules, exc_info - if fullname in modules: - return modules[fullname] - modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] - for path in self._module_choices: - realname = path % modname - try: - __import__(realname) - except ImportError: - exc_type, exc_value, tb = exc_info() - # since we only establish the entry in sys.modules at the - # very 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 - # fake module name and that import will succeed since this - # one already has a temporary entry in the modules dict. - # Since this one "succeeded" temporarily that second - # invocation now will have created a fullname entry in - # sys.modules which we have to kill. - modules.pop(fullname, None) - if self.is_important_traceback(realname, tb): - raise exc_type, exc_value, tb - continue - module = modules[fullname] = modules[realname] - if '.' not in modname: - setattr(modules[__name__], modname, module) - return module - raise ImportError('No module named %s' % fullname) - - def is_important_traceback(self, important_module, tb): - """Walks a traceback's frames and checks if any of the frames - originated in the given important module. If that is the case - then we were able to import the module itself but apparently - something went wrong when the module was imported. (Eg: import - of an import failed). - """ - # Why can we access f_globals' __name__ here and the value is - # not None? I honestly don't know but here is my thinking. - # The module owns a reference to globals and the frame has one. - # Each function only keeps a reference to the globals not do the - # module which normally causes the problem that when the module - # shuts down all globals are set to None. Now however when the - # import system fails Python takes the short way out and does not - # actually properly shut down the module by Noneing the values - # but by just removing the entry from sys.modules. This means - # that the regular reference based cleanup kicks in. - # - # The good thing: At worst we will swallow an exception we should - # not and the error message will be messed up. However I think - # this should be sufficiently reliable. - while tb is not None: - if tb.tb_frame.f_globals.get('__name__') == important_module: - return True - tb = tb.tb_next - return False +def setup(): + from ..exthook import ExtensionImporter + importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__) + importer.install() -_ExtensionImporter() -del _ExtensionImporter +setup() +del setup diff --git a/flask/exthook.py b/flask/exthook.py new file mode 100644 index 00000000..bb1deb29 --- /dev/null +++ b/flask/exthook.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +""" + flask.exthook + ~~~~~~~~~~~~~ + + Redirect imports for extensions. This module basically makes it possible + for us to transition from flaskext.foo to flask_foo without having to + force all extensions to upgrade at the same time. + + When a user does ``from flask.ext.foo import bar`` it will attempt to + import ``from flask_foo import bar`` first and when that fails it will + try to import ``from flaskext.foo import bar``. + + We're switching from namespace packages because it was just too painful for + everybody involved. + + This is used by `flask.ext`. + + :copyright: (c) 2011 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys +import os + + +class ExtensionImporter(object): + """This importer redirects imports from this submodule to other locations. + This makes it possible to transition from the old flaskext.name to the + newer flask_name without people having a hard time. + """ + + def __init__(self, module_choices, wrapper_module): + self.module_choices = module_choices + self.wrapper_module = wrapper_module + self.prefix = wrapper_module + '.' + self.prefix_cutoff = wrapper_module.count('.') + 1 + + def __eq__(self, other): + return self.__class__.__module__ == other.__class__.__module__ and \ + self.__class__.__name__ == other.__class__.__name__ and \ + self.wrapper_module == other.wrapper_module and \ + self.module_choices == other.module_choices + + def __ne__(self, other): + return not self.__eq__(other) + + def install(self): + sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] + + def find_module(self, fullname, path=None): + if fullname.startswith(self.prefix): + return self + + def load_module(self, fullname): + if fullname in sys.modules: + return sys.modules[fullname] + modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] + for path in self.module_choices: + realname = path % modname + try: + __import__(realname) + 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 + # 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 + # fake module name and that import will succeed since this + # one already has a temporary entry in the modules dict. + # Since this one "succeeded" temporarily that second + # invocation now will have created a fullname entry in + # sys.modules which we have to kill. + sys.modules.pop(fullname, None) + + # 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 + if self.is_important_traceback(realname, tb): + raise exc_type, exc_value, tb.tb_next + continue + module = sys.modules[fullname] = sys.modules[realname] + if '.' not in modname: + setattr(sys.modules[self.wrapper_module], modname, module) + return module + raise ImportError('No module named %s' % fullname) + + def is_important_traceback(self, important_module, tb): + """Walks a traceback's frames and checks if any of the frames + originated in the given important module. If that is the case then we + were able to import the module itself but apparently something went + wrong when the module was imported. (Eg: import of an import failed). + """ + while tb is not None: + if self.is_important_frame(important_module, tb): + return True + tb = tb.tb_next + return False + + def is_important_frame(self, important_module, tb): + """Checks a single frame if it's important.""" + g = tb.tb_frame.f_globals + if '__name__' not in g: + return False + + module_name = g['__name__'] + + # Python 2.7 Behavior. Modules are cleaned up late so the + # name shows up properly here. Success! + if module_name == important_module: + return True + + # Some python verisons 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) + test_string = os.path.sep + important_module.replace('.', os.path.sep) + return test_string + '.py' in filename or \ + test_string + os.path.sep + '__init__.py' in filename diff --git a/flask/testsuite/ext.py b/flask/testsuite/ext.py index c621bcf5..034ab5be 100644 --- a/flask/testsuite/ext.py +++ b/flask/testsuite/ext.py @@ -35,8 +35,8 @@ class ExtImportHookTestCase(FlaskTestCase): import_hooks = 0 for item in sys.meta_path: cls = type(item) - if cls.__module__ == 'flask.ext' and \ - cls.__name__ == '_ExtensionImporter': + if cls.__module__ == 'flask.exthook' and \ + cls.__name__ == 'ExtensionImporter': import_hooks += 1 self.assert_equal(import_hooks, 1) @@ -104,6 +104,18 @@ class ExtImportHookTestCase(FlaskTestCase): with self.assert_raises(ImportError): import flask.ext.broken + def test_no_error_swallowing(self): + try: + import flask.ext.broken + except ImportError: + exc_type, exc_value, tb = sys.exc_info() + self.assert_(exc_type is ImportError) + self.assert_equal(str(exc_value), 'No module named missing_module') + self.assert_(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) + def suite(): suite = unittest.TestSuite() diff --git a/scripts/flaskext_compat.py b/scripts/flaskext_compat.py index bb3ada03..40c8c6b5 100644 --- a/scripts/flaskext_compat.py +++ b/scripts/flaskext_compat.py @@ -15,22 +15,33 @@ :license: BSD, see LICENSE for more details. """ import sys +import os import imp -ext_module = imp.new_module('flask.ext') -ext_module.__path__ = [] -ext_module.__package__ = ext_module.__name__ - - -class _ExtensionImporter(object): - """This importer redirects imports from the flask.ext module to other - locations. For implementation details see the code in Flask 0.8 - that does the same. +class ExtensionImporter(object): + """This importer redirects imports from this submodule to other locations. + This makes it possible to transition from the old flaskext.name to the + newer flask_name without people having a hard time. """ - _module_choices = ['flask_%s', 'flaskext.%s'] - prefix = ext_module.__name__ + '.' - prefix_cutoff = prefix.count('.') + + def __init__(self, module_choices, wrapper_module): + self.module_choices = module_choices + self.wrapper_module = wrapper_module + self.prefix = wrapper_module + '.' + self.prefix_cutoff = wrapper_module.count('.') + 1 + + def __eq__(self, other): + return self.__class__.__module__ == other.__class__.__module__ and \ + self.__class__.__name__ == other.__class__.__name__ and \ + self.wrapper_module == other.wrapper_module and \ + self.module_choices == other.module_choices + + def __ne__(self, other): + return not self.__eq__(other) + + def install(self): + sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self] def find_module(self, fullname, path=None): if fullname.startswith(self.prefix): @@ -40,34 +51,74 @@ class _ExtensionImporter(object): if fullname in sys.modules: return sys.modules[fullname] modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] - for path in self._module_choices: + for path in self.module_choices: realname = path % modname try: __import__(realname) 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 + # 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 + # fake module name and that import will succeed since this + # one already has a temporary entry in the modules dict. + # Since this one "succeeded" temporarily that second + # invocation now will have created a fullname entry in + # sys.modules which we have to kill. sys.modules.pop(fullname, None) + + # 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 if self.is_important_traceback(realname, tb): - raise exc_type, exc_value, tb + raise exc_type, exc_value, tb.tb_next continue module = sys.modules[fullname] = sys.modules[realname] if '.' not in modname: - setattr(ext_module, modname, module) + setattr(sys.modules[self.wrapper_module], modname, module) return module raise ImportError('No module named %s' % fullname) def is_important_traceback(self, important_module, tb): + """Walks a traceback's frames and checks if any of the frames + originated in the given important module. If that is the case then we + were able to import the module itself but apparently something went + wrong when the module was imported. (Eg: import of an import failed). + """ while tb is not None: - if tb.tb_frame.f_globals.get('__name__') == important_module: + if self.is_important_frame(important_module, tb): return True tb = tb.tb_next return False + def is_important_frame(self, important_module, tb): + """Checks a single frame if it's important.""" + g = tb.tb_frame.f_globals + if '__name__' not in g: + return False + + module_name = g['__name__'] + + # Python 2.7 Behavior. Modules are cleaned up late so the + # name shows up properly here. Success! + if module_name == important_module: + return True + + # Some python verisons 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) + test_string = os.path.sep + important_module.replace('.', os.path.sep) + return test_string + '.py' in filename or \ + test_string + os.path.sep + '__init__.py' in filename + def activate(): - """Activates the compatibility system.""" import flask - if hasattr(flask, 'ext'): - return - sys.modules['flask.ext'] = flask.ext = ext_module - sys.meta_path.append(_ExtensionImporter()) + ext_module = imp.new_module('flask.ext') + ext_module.__path__ = [] + flask.ext = sys.modules['flask.ext'] = ext_module + importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], 'flask.ext') + importer.install() From 04b90f5ad47d04bace76c18f634d54b0da4b91ce Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 27 Sep 2011 14:36:06 +0200 Subject: [PATCH 0197/3143] Parentheses are for Python3 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 64b24533..14597afe 100644 --- a/setup.py +++ b/setup.py @@ -71,9 +71,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 585ff1db3db8569f16536aea75f64e1dd927041c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 29 Sep 2011 23:33:46 +0200 Subject: [PATCH 0198/3143] This will be Rakija --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1801fc26..e1a3733a 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. Version 0.8 ----------- -Relase date to be decided, codename to be chosen. +Released on September 29th 2011, codename Rakija - Refactored session support into a session interface so that the implementation of the sessions can be changed without From d5e10e4685f54dde5ffc27c4f55a19fb23f7a536 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 29 Sep 2011 23:34:02 +0200 Subject: [PATCH 0199/3143] Bump version number to 0.8 --- 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 c1076c33..73019aa8 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.8-dev' +__version__ = '0.8' # 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 14597afe..4e221bd9 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ class run_audit(Command): setup( name='Flask', - version='0.8-dev', + version='0.8', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 3765cc2e9e6ea2d227f898d5b176aa50680e491c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 29 Sep 2011 23:36:57 +0200 Subject: [PATCH 0200/3143] 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 766522cb587850f643f3bb6cb011735ae6a98510 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Oct 2011 01:06:22 +0200 Subject: [PATCH 0201/3143] Prepare for an 0.8.1 release --- CHANGES | 5 +++++ flask/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e1a3733a..ba50d0d9 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.8.1 +------------- + +Bugfix release, release date to be decided + Version 0.8 ----------- diff --git a/flask/__init__.py b/flask/__init__.py index 73019aa8..04d7d1f2 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.8.1-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..af36a8bb 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ class run_audit(Command): setup( name='Flask', - version='0.8', + version='0.8.1-dev', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 0dd9dc37b6618b8091c2a0f849f5f3143dc6eafc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 2 Oct 2011 01:08:54 +0200 Subject: [PATCH 0202/3143] Fixed an issue with an unused module for Python 2.5 (flask.session) --- CHANGES | 4 ++++ flask/session.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ba50d0d9..7640ac21 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ Version 0.8.1 Bugfix release, release date to be decided +- 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 + some problems for package managers. + Version 0.8 ----------- diff --git a/flask/session.py b/flask/session.py index 4d4d2cd6..1a43fdc1 100644 --- a/flask/session.py +++ b/flask/session.py @@ -13,7 +13,7 @@ from warnings import warn warn(DeprecationWarning('please use flask.sessions instead')) -from .sessions import * +from .sessions import SecureCookieSession, NullSession Session = SecureCookieSession _NullSession = NullSession From b51fbdc8e00ea777dcfdfa39ae789206ecfdb0a7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 6 Oct 2011 10:49:12 -0400 Subject: [PATCH 0203/3143] Removed a newline --- flask/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index be718cc6..f11c3ddd 100644 --- a/flask/views.py +++ b/flask/views.py @@ -15,7 +15,6 @@ http_method_funcs = frozenset(['get', 'post', 'head', 'options', 'delete', 'put', 'trace']) - class View(object): """Alternative way to use view functions. A subclass has to implement :meth:`dispatch_request` which is called with the view arguments from From 1759d8e4d80df3c3dc709c9bace2eb07d5685596 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 6 Oct 2011 10:57:03 -0400 Subject: [PATCH 0204/3143] 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 0205/3143] 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 0206/3143] 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 0207/3143] 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 0208/3143] 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 0209/3143] 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 0210/3143] 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 0211/3143] 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 0212/3143] 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 0213/3143] 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 0214/3143] 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 0215/3143] 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 0216/3143] 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 0217/3143] 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 0218/3143] 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 0219/3143] 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 0220/3143] 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 0221/3143] 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 0222/3143] 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 0223/3143] 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 0224/3143] 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 0225/3143] 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 0226/3143] 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 0227/3143] 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 0228/3143] 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 0229/3143] 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 0230/3143] 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 0231/3143] 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 0232/3143] 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 0233/3143] 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 0234/3143] 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 0235/3143] 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 0236/3143] 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 0237/3143] 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 0238/3143] 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 0239/3143] 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 0240/3143] 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 0241/3143] [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 0242/3143] 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 0243/3143] 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 0244/3143] 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 0245/3143] 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 0246/3143] 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 0247/3143] 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 0248/3143] 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 0249/3143] 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 0250/3143] 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 0251/3143] 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 0252/3143] 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 0253/3143] 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 0254/3143] 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 0255/3143] 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 0256/3143] 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 0257/3143] 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 0258/3143] 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 0259/3143] 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 0260/3143] 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 0261/3143] 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 0262/3143] 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 0263/3143] 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 0264/3143] 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 0265/3143] 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 0266/3143] 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 0267/3143] 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 0268/3143] 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 0269/3143] 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 0270/3143] 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 0271/3143] 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 0272/3143] 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 0273/3143] 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 0274/3143] 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 0275/3143] 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 0276/3143] 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 0277/3143] 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 0278/3143] 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 0279/3143] 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 0280/3143] 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 0281/3143] 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 0282/3143] 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 0283/3143] 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 0284/3143] 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 0285/3143] 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 0286/3143] 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 0287/3143] 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 0288/3143] 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 0289/3143] 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 0290/3143] 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 0291/3143] 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 0292/3143] 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 0293/3143] 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 0294/3143] 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 0295/3143] 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 0296/3143] 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 0297/3143] 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 0298/3143] 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 0299/3143] 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 0300/3143] 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 0301/3143] 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 0302/3143] 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 0303/3143] 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 0304/3143] 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 0305/3143] 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 0306/3143] 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 0307/3143] 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 0308/3143] 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 0309/3143] 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 0310/3143] 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 0311/3143] 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 0312/3143] 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 0313/3143] 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 0314/3143] 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 0315/3143] 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 0316/3143] 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 0317/3143] 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 0318/3143] 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 0319/3143] 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 0320/3143] 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 0321/3143] 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 0322/3143] 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 0323/3143] 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 0324/3143] 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 0325/3143] 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 0326/3143] 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 0327/3143] 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 0328/3143] 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 0329/3143] 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 0330/3143] 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 0331/3143] 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 0332/3143] 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 0333/3143] 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 0334/3143] 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 0335/3143] 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 0336/3143] 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 0337/3143] 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 0338/3143] 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 0339/3143] 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 0340/3143] 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 0341/3143] 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 0342/3143] 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 0343/3143] 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 0344/3143] 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 0345/3143] 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 0346/3143] 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 0347/3143] 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 0348/3143] 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 0349/3143] 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 0350/3143] 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 0351/3143] 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 0352/3143] 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 0353/3143] 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 0354/3143] 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 0355/3143] 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 0356/3143] 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 0357/3143] 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 0358/3143] 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 0359/3143] 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 0360/3143] 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 0361/3143] 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 0362/3143] 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 0363/3143] 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 0364/3143] 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 0365/3143] 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 0366/3143] 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 0367/3143] 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 0368/3143] 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 0369/3143] 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 0370/3143] 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 0371/3143] 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 0372/3143] 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 0373/3143] 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 0374/3143] 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 0375/3143] 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 0376/3143] 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 0377/3143] 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 0378/3143] 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 0379/3143] 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 0380/3143] 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 0381/3143] 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 0382/3143] 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 0383/3143] 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 0384/3143] 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 0385/3143] 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 0386/3143] 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 0387/3143] 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 0388/3143] 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 0389/3143] 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 0390/3143] 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 0391/3143] 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 0392/3143] 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 0393/3143] 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 0394/3143] 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 0395/3143] 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 0396/3143] 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 0397/3143] 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 0398/3143] 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 19def9606ac50bd308ea283e283cbcf62498d6c7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher <armin.ronacher@active-4.com> Date: Sun, 1 Jul 2012 12:08:38 +0100 Subject: [PATCH 0399/3143] This is 0.8.1 --- CHANGES | 2 +- flask/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 7640ac21..014d579f 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. Version 0.8.1 ------------- -Bugfix release, release date to be decided +Bugfix release, released on July 1th 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 diff --git a/flask/__init__.py b/flask/__init__.py index 04d7d1f2..98fcbe13 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.8.1-dev' +__version__ = '0.8.1' # 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 af36a8bb..2aad7b57 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ class run_audit(Command): setup( name='Flask', - version='0.8.1-dev', + version='0.8.1', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', 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 0400/3143] 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 0401/3143] 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 0402/3143] 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 0403/3143] 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 0404/3143] 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 0405/3143] 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 0406/3143] 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 0407/3143] 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 0408/3143] 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 0409/3143] 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 0410/3143] 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 0411/3143] 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 0412/3143] 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 0413/3143] 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 0414/3143] 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 0415/3143] 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 0416/3143] 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 0417/3143] 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 0418/3143] 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 0419/3143] 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 0420/3143] 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 0421/3143] 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 0422/3143] 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 0423/3143] 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 0424/3143] 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 0425/3143] 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 0426/3143] 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 0427/3143] 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 0428/3143] 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 0429/3143] 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 0430/3143] 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 0431/3143] 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 0432/3143] 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 0433/3143] 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 0434/3143] 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 0435/3143] 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 0436/3143] 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 0437/3143] 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 0438/3143] 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 0439/3143] 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 0440/3143] 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 0441/3143] 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 0442/3143] 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 0443/3143] 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 0444/3143] 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 0445/3143] 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 0446/3143] 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 0447/3143] 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 0448/3143] 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 0449/3143] 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 0450/3143] 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 0451/3143] 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 0452/3143] 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 0453/3143] 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 0454/3143] 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 0455/3143] 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 0456/3143] 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 0457/3143] 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 0458/3143] 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 0459/3143] 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 0460/3143] 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 0461/3143] 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 0462/3143] 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 0463/3143] 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 0464/3143] 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 0465/3143] 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 0466/3143] 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 0467/3143] 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 0468/3143] 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 0469/3143] 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 0470/3143] 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 0471/3143] 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 0472/3143] 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 0473/3143] 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 0474/3143] 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 0475/3143] 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 0476/3143] 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 0477/3143] 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 0478/3143] 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 0479/3143] 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 0480/3143] 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 0481/3143] 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 0482/3143] 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 0483/3143] 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 0484/3143] 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 0485/3143] 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 0486/3143] 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 0487/3143] 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 0488/3143] 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 0489/3143] 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 0490/3143] 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 0491/3143] 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 0492/3143] 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 0493/3143] 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 0494/3143] 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 0495/3143] 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 0496/3143] 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 0497/3143] 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 0498/3143] 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 0499/3143] 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 0500/3143] 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 0501/3143] 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 0502/3143] 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 0503/3143] 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 0504/3143] 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 0505/3143] 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 0506/3143] 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 0507/3143] 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 0508/3143] 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 0509/3143] 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 0510/3143] 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 0511/3143] 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 0512/3143] 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 0513/3143] 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 0514/3143] 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 0515/3143] 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 0516/3143] 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 0517/3143] 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 0518/3143] 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 0519/3143] 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 0520/3143] 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 0521/3143] 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 0522/3143] 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 0523/3143] 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 0524/3143] 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 0525/3143] 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 0526/3143] 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 0527/3143] 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 0528/3143] 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 0529/3143] 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 0530/3143] 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 0531/3143] 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 0532/3143] 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 0533/3143] 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 0534/3143] 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 0535/3143] 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 0536/3143] 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 0537/3143] 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 0538/3143] 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 0539/3143] 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 0540/3143] 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 0541/3143] 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 0542/3143] 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 0543/3143] 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 0544/3143] 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 0545/3143] 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 0546/3143] 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 0547/3143] 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 0548/3143] 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 0549/3143] 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 0550/3143] 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 0551/3143] 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 0552/3143] 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 0553/3143] 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 0554/3143] 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 0555/3143] 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 0556/3143] 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 0557/3143] 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 0558/3143] 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 0559/3143] 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 0560/3143] 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 0561/3143] 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 0562/3143] 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 0563/3143] 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 0564/3143] 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 0565/3143] 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 0566/3143] 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 0567/3143] 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 0568/3143] 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 0569/3143] 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 0570/3143] 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 0571/3143] 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 0572/3143] 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 0573/3143] 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 0574/3143] 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 0575/3143] 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 0576/3143] 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 0577/3143] 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 0578/3143] 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 0579/3143] 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 0580/3143] 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 0581/3143] 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 0582/3143] 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 0583/3143] 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 0584/3143] 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 0585/3143] 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 0586/3143] 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 0587/3143] 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 0588/3143] 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 0589/3143] 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 0590/3143] 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 0591/3143] 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 0592/3143] 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 0593/3143] 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 0594/3143] 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 0595/3143] 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 0596/3143] 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 0597/3143] 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 0598/3143] 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 0599/3143] 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 0600/3143] 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 0601/3143] 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 0602/3143] 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 %} -