From 0da56d7f5cfb6a637129444bfe0d6bf2584363ac Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 18 Mar 2011 09:15:28 +0100 Subject: [PATCH] deprecated init_jinja_globals --- CHANGES | 3 +++ flask/app.py | 50 ++++++++++++++++++++++++++++++-------------- flask/helpers.py | 31 +++++++++++++++++++++++++++ tests/flask_tests.py | 20 ++++++++++++++++++ 4 files changed, 88 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 9420dac6..394cd39e 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,9 @@ Release date to be announced, codename to be selected - Added `teardown_request` decorator, for functions that should run at the end of a request regardless of whether an exception occurred. - Implemented :func:`flask.has_request_context` +- Deprecated `init_jinja_globals`. Override the + :meth:`~flask.Flask.create_jinja_environment` method instead to + achieve the same functionality. Version 0.6.1 ------------- diff --git a/flask/app.py b/flask/app.py index 49c8fe10..706ef229 100644 --- a/flask/app.py +++ b/flask/app.py @@ -24,7 +24,7 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \ MethodNotAllowed from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ - _tojson_filter, _endpoint_from_view_func + locked_cached_property, _tojson_filter, _endpoint_from_view_func from .wrappers import Request, Response from .config import ConfigAttribute, Config from .ctx import _RequestContext @@ -317,11 +317,6 @@ class Flask(_PackageBoundObject): endpoint='static', view_func=self.send_static_file) - #: The Jinja2 environment. It is created from the - #: :attr:`jinja_options`. - self.jinja_env = self.create_jinja_environment() - self.init_jinja_globals() - @property def propagate_exceptions(self): """Returns the value of the `PROPAGATE_EXCEPTIONS` configuration @@ -356,16 +351,43 @@ class Flask(_PackageBoundObject): self._logger = rv = create_logger(self) return rv + @locked_cached_property + def jinja_env(self): + """The Jinja2 environment used to load templates.""" + rv = self.create_jinja_environment() + + # Hack to support the init_jinja_globals method which is supported + # until 1.0 but has an API deficiency. + if getattr(self.init_jinja_globals, 'im_func', None) is not \ + Flask.init_jinja_globals.im_func: + from warnings import warn + warn(DeprecationWarning('This flask class uses a customized ' + 'init_jinja_globals() method which is deprecated. ' + 'Move the code from that method into the ' + 'create_jinja_environment() method instead.')) + self.__dict__['jinja_env'] = rv + self.init_jinja_globals() + + return rv + def create_jinja_environment(self): """Creates the Jinja2 environment based on :attr:`jinja_options` - and :meth:`select_jinja_autoescape`. + and :meth:`select_jinja_autoescape`. Since 0.7 this also adds + the Jinja2 globals and filters after initialization. Override + this function to customize the behavior. .. versionadded:: 0.5 """ options = dict(self.jinja_options) if 'autoescape' not in options: options['autoescape'] = self.select_jinja_autoescape - return Environment(loader=self.create_jinja_loader(), **options) + rv = Environment(loader=self.create_jinja_loader(), **options) + rv.globals.update( + url_for=url_for, + get_flashed_messages=get_flashed_messages + ) + rv.filters['tojson'] = _tojson_filter + return rv def create_jinja_loader(self): """Creates the loader for the Jinja2 environment. Can be used to @@ -376,17 +398,13 @@ class Flask(_PackageBoundObject): return _DispatchingJinjaLoader(self) def init_jinja_globals(self): - """Called directly after the environment was created to inject - some defaults (like `url_for`, `get_flashed_messages` and the - `tojson` filter. + """Deprecated. Used to initialize the Jinja2 globals. .. versionadded:: 0.5 + .. versionchanged:: 0.7 + This method is deprecated with 0.7. Override + :meth:`create_jinja_environment` instead. """ - self.jinja_env.globals.update( - url_for=url_for, - get_flashed_messages=get_flashed_messages - ) - self.jinja_env.filters['tojson'] = _tojson_filter def select_jinja_autoescape(self, filename): """Returns `True` if autoescaping should be active for the given diff --git a/flask/helpers.py b/flask/helpers.py index ed8a5d5c..7539b248 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -15,6 +15,7 @@ import posixpath import mimetypes from time import time from zlib import adler32 +from threading import RLock # try to load the best simplejson implementation available. If JSON # is not installed, we add a failing class. @@ -58,6 +59,10 @@ else: _tojson_filter = json.dumps +# sentinel +_missing = object() + + # what separators does this operating system provide that are not a slash? # this is used by the send_from_directory function to ensure that nobody is # able to access files from outside the filesystem. @@ -435,6 +440,32 @@ def _get_package_path(name): return os.getcwd() +class locked_cached_property(object): + """A decorator that converts a function into a lazy property. The + function wrapped is called the first time to retrieve the result + and then that calculated result is used the next time you access + the value. Works like the one in Werkzeug but has a lock for + thread safety. + """ + + def __init__(self, func, name=None, doc=None): + self.__name__ = name or func.__name__ + self.__module__ = func.__module__ + self.__doc__ = doc or func.__doc__ + self.func = func + self.lock = RLock() + + def __get__(self, obj, type=None): + if obj is None: + return self + with self.lock: + value = obj.__dict__.get(self.__name__, _missing) + if value is _missing: + value = self.func(obj) + obj.__dict__[self.__name__] = value + return value + + class _PackageBoundObject(object): def __init__(self, import_name): diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 19168180..a9ad9464 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1564,6 +1564,25 @@ class TestSignals(unittest.TestCase): flask.got_request_exception.disconnect(record, app) +class DeprecationsTestCase(unittest.TestCase): + + def test_init_jinja_globals(self): + class MyFlask(flask.Flask): + def init_jinja_globals(self): + self.jinja_env.globals['foo'] = '42' + + with catch_warnings() as log: + app = MyFlask(__name__) + @app.route('/') + def foo(): + return app.jinja_env.globals['foo'] + + c = app.test_client() + assert c.get('/').data == '42' + assert len(log) == 1 + assert 'init_jinja_globals' in str(log[0]['message']) + + def suite(): from minitwit_tests import MiniTwitTestCase from flaskr_tests import FlaskrTestCase @@ -1576,6 +1595,7 @@ def suite(): suite.addTest(unittest.makeSuite(LoggingTestCase)) suite.addTest(unittest.makeSuite(ConfigTestCase)) suite.addTest(unittest.makeSuite(SubdomainTestCase)) + suite.addTest(unittest.makeSuite(DeprecationsTestCase)) if flask.json_available: suite.addTest(unittest.makeSuite(JSONTestCase)) if flask.signals_available: