Browse files

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

… first view was handled.
  • Loading branch information...
mitsuhiko committed Aug 7, 2011
1 parent 5ca17c8 commit 5500986971b28f270a27db633acf19984eee609e
Showing with 60 additions and 0 deletions.
  1. +3 −0 CHANGES
  2. +35 −0 flask/
  3. +22 −0 tests/
@@ -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
@@ -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.
@@ -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."""
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
rv = self.preprocess_request()
@@ -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:
+'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):

0 comments on commit 5500986

Please sign in to comment.