Flask in debug mode will now complain if views are attached after the first view was handled.
This commit is contained in:
parent
5ca17c86c0
commit
5500986971
3 changed files with 60 additions and 0 deletions
3
CHANGES
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
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()
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue