Browse Source

Flask in debug mode will now complain if views are attached after the first view was handled.

pull/300/head
Armin Ronacher 13 years ago
parent
commit
5500986971
  1. 3
      CHANGES
  2. 35
      flask/app.py
  3. 22
      tests/flask_tests.py

3
CHANGES

@ -18,6 +18,9 @@ Relase date to be decided, codename to be chosen.
show up normally in the traceback. show up normally in the traceback.
- Flask in debug mode is now detecting some common problems and tries to - Flask in debug mode is now detecting some common problems and tries to
warn you about them. 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 Version 0.7.3
------------- -------------

35
flask/app.py

@ -15,6 +15,7 @@ import sys
from threading import Lock from threading import Lock
from datetime import timedelta from datetime import timedelta
from itertools import chain from itertools import chain
from functools import update_wrapper
from werkzeug.datastructures import ImmutableDict from werkzeug.datastructures import ImmutableDict
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule
@ -38,6 +39,23 @@ from .signals import request_started, request_finished, got_request_exception, \
_logger_lock = Lock() _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): class Flask(_PackageBoundObject):
"""The flask object implements a WSGI application and acts as the central """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 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 #: app.url_map.converters['list'] = ListConverter
self.url_map = Map() 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 # register the static folder for the application. Do that even
# if the folder does not exist. First of all it might be created # if the folder does not exist. First of all it might be created
# while the server is running (usually happens during development) # while the server is running (usually happens during development)
@ -642,6 +664,7 @@ class Flask(_PackageBoundObject):
self.register_blueprint(module, **options) self.register_blueprint(module, **options)
@setupmethod
def register_blueprint(self, blueprint, **options): def register_blueprint(self, blueprint, **options):
"""Registers a blueprint on the application. """Registers a blueprint on the application.
@ -659,6 +682,7 @@ class Flask(_PackageBoundObject):
first_registration = True first_registration = True
blueprint.register(self, options, first_registration) blueprint.register(self, options, first_registration)
@setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options): def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
"""Connects a URL rule. Works exactly like the :meth:`route` """Connects a URL rule. Works exactly like the :meth:`route`
decorator. If a view_func is provided it will be registered with the decorator. If a view_func is provided it will be registered with the
@ -812,6 +836,7 @@ class Flask(_PackageBoundObject):
return f return f
return decorator return decorator
@setupmethod
def endpoint(self, endpoint): def endpoint(self, endpoint):
"""A decorator to register a function as an endpoint. """A decorator to register a function as an endpoint.
Example:: Example::
@ -827,6 +852,7 @@ class Flask(_PackageBoundObject):
return f return f
return decorator return decorator
@setupmethod
def errorhandler(self, code_or_exception): def errorhandler(self, code_or_exception):
"""A decorator that is used to register a function give a given """A decorator that is used to register a function give a given
error code. Example:: error code. Example::
@ -877,6 +903,7 @@ class Flask(_PackageBoundObject):
""" """
self._register_error_handler(None, code_or_exception, f) self._register_error_handler(None, code_or_exception, f)
@setupmethod
def _register_error_handler(self, key, code_or_exception, f): def _register_error_handler(self, key, code_or_exception, f):
if isinstance(code_or_exception, HTTPException): if isinstance(code_or_exception, HTTPException):
code_or_exception = code_or_exception.code code_or_exception = code_or_exception.code
@ -889,6 +916,7 @@ class Flask(_PackageBoundObject):
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \ self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
.append((code_or_exception, f)) .append((code_or_exception, f))
@setupmethod
def template_filter(self, name=None): def template_filter(self, name=None):
"""A decorator that is used to register custom template filter. """A decorator that is used to register custom template filter.
You can specify a name for the filter, otherwise the function You can specify a name for the filter, otherwise the function
@ -906,11 +934,13 @@ class Flask(_PackageBoundObject):
return f return f
return decorator return decorator
@setupmethod
def before_request(self, f): def before_request(self, f):
"""Registers a function to run before each request.""" """Registers a function to run before each request."""
self.before_request_funcs.setdefault(None, []).append(f) self.before_request_funcs.setdefault(None, []).append(f)
return f return f
@setupmethod
def after_request(self, f): def after_request(self, f):
"""Register a function to be run after each request. Your function """Register a function to be run after each request. Your function
must take one parameter, a :attr:`response_class` object and return 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) self.after_request_funcs.setdefault(None, []).append(f)
return f return f
@setupmethod
def teardown_request(self, f): def teardown_request(self, f):
"""Register a function to be run at the end of each request, """Register a function to be run at the end of each request,
regardless of whether there was an exception or not. These functions 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) self.teardown_request_funcs.setdefault(None, []).append(f)
return f return f
@setupmethod
def context_processor(self, f): def context_processor(self, f):
"""Registers a template context processor function.""" """Registers a template context processor function."""
self.template_context_processors[None].append(f) self.template_context_processors[None].append(f)
return f return f
@setupmethod
def url_value_preprocessor(self, f): def url_value_preprocessor(self, f):
"""Registers a function as URL value preprocessor for all view """Registers a function as URL value preprocessor for all view
functions of the application. It's called before the view functions 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) self.url_value_preprocessors.setdefault(None, []).append(f)
return f return f
@setupmethod
def url_defaults(self, f): def url_defaults(self, f):
"""Callback function for URL defaults for all view functions of the """Callback function for URL defaults for all view functions of the
application. It's called with the endpoint and values and should application. It's called with the endpoint and values and should
@ -1097,6 +1131,7 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.7 .. versionadded:: 0.7
""" """
self._got_first_request = True
try: try:
request_started.send(self) request_started.send(self)
rv = self.preprocess_request() rv = self.preprocess_request()

22
tests/flask_tests.py

@ -944,6 +944,28 @@ class BasicFunctionalityTestCase(unittest.TestCase):
self.assertEqual(c.get('/de/about').data, '/foo') self.assertEqual(c.get('/de/about').data, '/foo')
self.assertEqual(c.get('/foo').data, '/en/about') 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): class JSONTestCase(unittest.TestCase):

Loading…
Cancel
Save