Skip to content

Commit

Permalink
Flask in debug mode will now complain if views are attached after the…
Browse files Browse the repository at this point in the history
… first view was handled.
  • Loading branch information
mitsuhiko committed Aug 7, 2011
1 parent 5ca17c8 commit 5500986
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGES
Expand Up @@ -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
-------------
Expand Down
35 changes: 35 additions & 0 deletions flask/app.py
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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::
Expand All @@ -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::
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down
22 changes: 22 additions & 0 deletions tests/flask_tests.py
Expand Up @@ -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):

Expand Down

0 comments on commit 5500986

Please sign in to comment.