From 02a131746074203dc8d747308f4d2c7ece8aa743 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 7 Aug 2011 12:43:38 +0200 Subject: [PATCH] Added the ability to trigger functions before the first request to the application --- CHANGES | 2 ++ flask/app.py | 52 ++++++++++++++++++++++++++++++++++++++++++-- flask/blueprints.py | 7 ++++++ tests/flask_tests.py | 15 +++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e4300363..cc8198d2 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Relase date to be decided, codename to be chosen. - 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. +- Added the ability to register callbacks that are only triggered once at + the beginning of the first request. (:meth:`Flask.before_first_request`) Version 0.7.3 ------------- diff --git a/flask/app.py b/flask/app.py index df147863..d299b4fb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -291,6 +291,13 @@ class Flask(_PackageBoundObject): #: function here, use the :meth:`before_request` decorator. self.before_request_funcs = {} + #: A lists of functions that should be called at the beginning of the + #: first request to this instance. To register a function here, use + #: the :meth:`before_first_request` decorator. + #: + #: .. versionadded:: 0.8 + self.before_first_request_funcs = [] + #: A dictionary with lists of functions that should be called after #: each request. The key of the dictionary is the name of the blueprint #: this function is active for, `None` for all requests. This can for @@ -386,6 +393,7 @@ class Flask(_PackageBoundObject): # tracks internally if the application already handled at least one # request. self._got_first_request = False + self._before_request_lock = Lock() # register the static folder for the application. Do that even # if the folder does not exist. First of all it might be created @@ -474,6 +482,15 @@ class Flask(_PackageBoundObject): return rv + @property + def got_first_request(self): + """This attribute is set to `True` if the application started + handling the first request. + + .. versionadded:: 0.8 + """ + return self._got_first_request + def create_jinja_environment(self): """Creates the Jinja2 environment based on :attr:`jinja_options` and :meth:`select_jinja_autoescape`. Since 0.7 this also adds @@ -581,7 +598,13 @@ class Flask(_PackageBoundObject): self.debug = options.pop('debug') options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) - return run_simple(host, port, self, **options) + try: + run_simple(host, port, self, **options) + finally: + # reset the first request information if the development server + # resetted normally. This makes it possible to restart the server + # without reloader and that stuff from an interactive shell. + self._got_first_request = False def test_client(self, use_cookies=True): """Creates a test client for this application. For information @@ -940,6 +963,15 @@ class Flask(_PackageBoundObject): self.before_request_funcs.setdefault(None, []).append(f) return f + @setupmethod + def before_first_request(self, f): + """Registers a function to be run before the first request to this + instance of the application. + + .. versionadded:: 0.8 + """ + self.before_first_request_funcs.append(f) + @setupmethod def after_request(self, f): """Register a function to be run after each request. Your function @@ -1131,7 +1163,7 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.7 """ - self._got_first_request = True + self.try_trigger_before_first_request_functions() try: request_started.send(self) rv = self.preprocess_request() @@ -1144,6 +1176,22 @@ class Flask(_PackageBoundObject): request_finished.send(self, response=response) return response + def try_trigger_before_first_request_functions(self): + """Called before each request and will ensure that it triggers + the :attr:`before_first_request_funcs` and only exactly once per + application instance (which means process usually). + + .. versionadded:: 0.8 + """ + if self._got_first_request: + return + with self._before_request_lock: + if self._got_first_request: + return + self._got_first_request = True + for func in self.before_first_request_funcs: + func() + def make_default_options_response(self): """This method is called to create the default `OPTIONS` response. This can be changed through subclassing to change the default diff --git a/flask/blueprints.py b/flask/blueprints.py index 0c3a7d0f..075961ab 100644 --- a/flask/blueprints.py +++ b/flask/blueprints.py @@ -199,6 +199,13 @@ class Blueprint(_PackageBoundObject): .setdefault(None, []).append(f)) return f + def before_app_first_request(self, f): + """Like :meth:`Flask.before_first_request`. Such a function is + executed before the first request to the application. + """ + self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) + return f + def after_request(self, f): """Like :meth:`Flask.after_request` but for a blueprint. This function is only executed after each request that is handled by a function of diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 59149286..467381eb 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -950,6 +950,7 @@ class BasicFunctionalityTestCase(unittest.TestCase): @app.route('/') def index(): return 'Awesome' + self.assert_(not app.got_first_request) self.assertEqual(app.test_client().get('/').data, 'Awesome') try: @app.route('/foo') @@ -965,6 +966,20 @@ class BasicFunctionalityTestCase(unittest.TestCase): def working(): return 'Meh' self.assertEqual(app.test_client().get('/foo').data, 'Meh') + self.assert_(app.got_first_request) + + def test_before_first_request_functions(self): + got = [] + app = flask.Flask(__name__) + @app.before_first_request + def foo(): + got.append(42) + c = app.test_client() + c.get('/') + self.assertEqual(got, [42]) + c.get('/') + self.assertEqual(got, [42]) + self.assert_(app.got_first_request) class JSONTestCase(unittest.TestCase):