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.
- 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
-------------

35
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()

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('/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):

Loading…
Cancel
Save