Skip to content
This repository
Browse code

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

… first view was handled.
  • Loading branch information...
commit 5500986971b28f270a27db633acf19984eee609e 1 parent 5ca17c8
Armin Ronacher authored August 07, 2011
3  CHANGES
@@ -18,6 +18,9 @@ Relase date to be decided, codename to be chosen.
18 18
   show up normally in the traceback.
19 19
 - Flask in debug mode is now detecting some common problems and tries to
20 20
   warn you about them.
  21
+- Flask in debug mode will now complain with an assertion error if a view
  22
+  was attached after the first request was handled.  This gives earlier
  23
+  feedback when users forget to import view code ahead of time.
21 24
 
22 25
 Version 0.7.3
23 26
 -------------
35  flask/app.py
@@ -15,6 +15,7 @@
15 15
 from threading import Lock
16 16
 from datetime import timedelta
17 17
 from itertools import chain
  18
+from functools import update_wrapper
18 19
 
19 20
 from werkzeug.datastructures import ImmutableDict
20 21
 from werkzeug.routing import Map, Rule
@@ -38,6 +39,23 @@
38 39
 _logger_lock = Lock()
39 40
 
40 41
 
  42
+def setupmethod(f):
  43
+    """Wraps a method so that it performs a check in debug mode if the
  44
+    first request was already handled.
  45
+    """
  46
+    def wrapper_func(self, *args, **kwargs):
  47
+        if self.debug and self._got_first_request:
  48
+            raise AssertionError('A setup function was called after the '
  49
+                'first request was handled.  This usually indicates a bug '
  50
+                'in the application where a module was not imported '
  51
+                'and decorators or other functionality was called too late.\n'
  52
+                'To fix this make sure to import all your view modules, '
  53
+                'database models and everything related at a central place '
  54
+                'before the application starts serving requests.')
  55
+        return f(self, *args, **kwargs)
  56
+    return update_wrapper(wrapper_func, f)
  57
+
  58
+
41 59
 class Flask(_PackageBoundObject):
42 60
     """The flask object implements a WSGI application and acts as the central
43 61
     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,
365 383
         #:    app.url_map.converters['list'] = ListConverter
366 384
         self.url_map = Map()
367 385
 
  386
+        # tracks internally if the application already handled at least one
  387
+        # request.
  388
+        self._got_first_request = False
  389
+
368 390
         # register the static folder for the application.  Do that even
369 391
         # if the folder does not exist.  First of all it might be created
370 392
         # while the server is running (usually happens during development)
@@ -642,6 +664,7 @@ def register_module(self, module, **options):
642 664
 
643 665
         self.register_blueprint(module, **options)
644 666
 
  667
+    @setupmethod
645 668
     def register_blueprint(self, blueprint, **options):
646 669
         """Registers a blueprint on the application.
647 670
 
@@ -659,6 +682,7 @@ def register_blueprint(self, blueprint, **options):
659 682
             first_registration = True
660 683
         blueprint.register(self, options, first_registration)
661 684
 
  685
+    @setupmethod
662 686
     def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
663 687
         """Connects a URL rule.  Works exactly like the :meth:`route`
664 688
         decorator.  If a view_func is provided it will be registered with the
@@ -812,6 +836,7 @@ def decorator(f):
812 836
             return f
813 837
         return decorator
814 838
 
  839
+    @setupmethod
815 840
     def endpoint(self, endpoint):
816 841
         """A decorator to register a function as an endpoint.
817 842
         Example::
@@ -827,6 +852,7 @@ def decorator(f):
827 852
             return f
828 853
         return decorator
829 854
 
  855
+    @setupmethod
830 856
     def errorhandler(self, code_or_exception):
831 857
         """A decorator that is used to register a function give a given
832 858
         error code.  Example::
@@ -877,6 +903,7 @@ def register_error_handler(self, code_or_exception, f):
877 903
         """
878 904
         self._register_error_handler(None, code_or_exception, f)
879 905
 
  906
+    @setupmethod
880 907
     def _register_error_handler(self, key, code_or_exception, f):
881 908
         if isinstance(code_or_exception, HTTPException):
882 909
             code_or_exception = code_or_exception.code
@@ -889,6 +916,7 @@ def _register_error_handler(self, key, code_or_exception, f):
889 916
             self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
890 917
                 .append((code_or_exception, f))
891 918
 
  919
+    @setupmethod
892 920
     def template_filter(self, name=None):
893 921
         """A decorator that is used to register custom template filter.
894 922
         You can specify a name for the filter, otherwise the function
@@ -906,11 +934,13 @@ def decorator(f):
906 934
             return f
907 935
         return decorator
908 936
 
  937
+    @setupmethod
909 938
     def before_request(self, f):
910 939
         """Registers a function to run before each request."""
911 940
         self.before_request_funcs.setdefault(None, []).append(f)
912 941
         return f
913 942
 
  943
+    @setupmethod
914 944
     def after_request(self, f):
915 945
         """Register a function to be run after each request.  Your function
916 946
         must take one parameter, a :attr:`response_class` object and return
@@ -922,6 +952,7 @@ def after_request(self, f):
922 952
         self.after_request_funcs.setdefault(None, []).append(f)
923 953
         return f
924 954
 
  955
+    @setupmethod
925 956
     def teardown_request(self, f):
926 957
         """Register a function to be run at the end of each request,
927 958
         regardless of whether there was an exception or not.  These functions
@@ -948,11 +979,13 @@ def teardown_request(self, f):
948 979
         self.teardown_request_funcs.setdefault(None, []).append(f)
949 980
         return f
950 981
 
  982
+    @setupmethod
951 983
     def context_processor(self, f):
952 984
         """Registers a template context processor function."""
953 985
         self.template_context_processors[None].append(f)
954 986
         return f
955 987
 
  988
+    @setupmethod
956 989
     def url_value_preprocessor(self, f):
957 990
         """Registers a function as URL value preprocessor for all view
958 991
         functions of the application.  It's called before the view functions
@@ -961,6 +994,7 @@ def url_value_preprocessor(self, f):
961 994
         self.url_value_preprocessors.setdefault(None, []).append(f)
962 995
         return f
963 996
 
  997
+    @setupmethod
964 998
     def url_defaults(self, f):
965 999
         """Callback function for URL defaults for all view functions of the
966 1000
         application.  It's called with the endpoint and values and should
@@ -1097,6 +1131,7 @@ def full_dispatch_request(self):
1097 1131
 
1098 1132
         .. versionadded:: 0.7
1099 1133
         """
  1134
+        self._got_first_request = True
1100 1135
         try:
1101 1136
             request_started.send(self)
1102 1137
             rv = self.preprocess_request()
22  tests/flask_tests.py
@@ -944,6 +944,28 @@ def something_else():
944 944
         self.assertEqual(c.get('/de/about').data, '/foo')
945 945
         self.assertEqual(c.get('/foo').data, '/en/about')
946 946
 
  947
+    def test_debug_mode_complains_after_first_request(self):
  948
+        app = flask.Flask(__name__)
  949
+        app.debug = True
  950
+        @app.route('/')
  951
+        def index():
  952
+            return 'Awesome'
  953
+        self.assertEqual(app.test_client().get('/').data, 'Awesome')
  954
+        try:
  955
+            @app.route('/foo')
  956
+            def broken():
  957
+                return 'Meh'
  958
+        except AssertionError, e:
  959
+            self.assert_('A setup function was called' in str(e))
  960
+        else:
  961
+            self.fail('Expected exception')
  962
+
  963
+        app.debug = False
  964
+        @app.route('/foo')
  965
+        def working():
  966
+            return 'Meh'
  967
+        self.assertEqual(app.test_client().get('/foo').data, 'Meh')
  968
+
947 969
 
948 970
 class JSONTestCase(unittest.TestCase):
949 971
 

0 notes on commit 5500986

Please sign in to comment.
Something went wrong with that request. Please try again.