Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Flask in debug mode will now complain if views are attached after the…

… first view was handled.
  • Loading branch information...
commit 5500986971b28f270a27db633acf19984eee609e 1 parent 5ca17c8
@mitsuhiko authored
Showing with 60 additions and 0 deletions.
  1. +3 −0  CHANGES
  2. +35 −0 flask/app.py
  3. +22 −0 tests/flask_tests.py
View
3  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
-------------
View
35 flask/app.py
@@ -15,6 +15,7 @@
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 @@
_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 @@ def __init__(self, import_name, static_path=None, static_url_path=None,
#: 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 @@ def register_module(self, module, **options):
self.register_blueprint(module, **options)
+ @setupmethod
def register_blueprint(self, blueprint, **options):
"""Registers a blueprint on the application.
@@ -659,6 +682,7 @@ def register_blueprint(self, blueprint, **options):
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 @@ def decorator(f):
return f
return decorator
+ @setupmethod
def endpoint(self, endpoint):
"""A decorator to register a function as an endpoint.
Example::
@@ -827,6 +852,7 @@ def decorator(f):
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 @@ def register_error_handler(self, code_or_exception, f):
"""
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 @@ def _register_error_handler(self, key, code_or_exception, f):
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 @@ def decorator(f):
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 @@ def after_request(self, f):
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 @@ def teardown_request(self, f):
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 @@ def url_value_preprocessor(self, f):
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 @@ def full_dispatch_request(self):
.. versionadded:: 0.7
"""
+ self._got_first_request = True
try:
request_started.send(self)
rv = self.preprocess_request()
View
22 tests/flask_tests.py
@@ -944,6 +944,28 @@ def something_else():
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):
Please sign in to comment.
Something went wrong with that request. Please try again.