From 67f4b0f31538832f06b9351d15e30c85f449ac7e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 24 Apr 2010 17:07:16 +0200 Subject: [PATCH] Modules are now instanciated with the package name. This makes it possible to load resources from the folder the module is located in. --- flask.py | 165 ++++++++++++++++++++++++------------------- tests/flask_tests.py | 4 +- 2 files changed, 96 insertions(+), 73 deletions(-) diff --git a/flask.py b/flask.py index c1a03f26..e07b7923 100644 --- a/flask.py +++ b/flask.py @@ -295,6 +295,43 @@ else: _tojson_filter = json.dumps +class _PackageBoundObject(object): + + def __init__(self, import_name): + #: the name of the package or module. Do not change this once + #: it was set by the constructor. + self.import_name = import_name + + #: where is the app root located? + self.root_path = _get_package_path(self.import_name) + + def open_resource(self, resource): + """Opens a resource from the application's resource folder. To see + how this works, consider the following folder structure:: + + /myapplication.py + /schemal.sql + /static + /style.css + /template + /layout.html + /index.html + + If you want to open the `schema.sql` file you would do the + following:: + + with app.open_resource('schema.sql') as f: + contents = f.read() + do_something_with(contents) + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + """ + if pkg_resources is None: + return open(os.path.join(self.root_path, resource), 'rb') + return pkg_resources.resource_stream(self.import_name, resource) + + class _ModuleSetupState(object): def __init__(self, app, url_prefix=None): @@ -302,44 +339,53 @@ class _ModuleSetupState(object): self.url_prefix = url_prefix -class Module(object): +class Module(_PackageBoundObject): """Container object that enables pluggable applications""" - def __init__(self, name, url_prefix=None): + def __init__(self, import_name, name=None, url_prefix=None): + if name is None: + assert '.' in import_name, 'name required if package name ' \ + 'does not point to a submodule' + name = import_name.rsplit('.', 1)[1] + _PackageBoundObject.__init__(self, import_name) self.name = name self.url_prefix = url_prefix self._register_events = [] def route(self, rule, **options): + """Like :meth:`flask.Flask.route` but for a module""" def decorator(f): self.add_url_rule(rule, f.__name__, f, **options) return f return decorator def add_url_rule(self, rule, endpoint, view_func=None, **options): - self._record(self._register_rule, (rule, endpoint, view_func, options)) + """Like :meth:`flask.Flask.add_url_rule` but for a module""" + def register_rule(state): + the_rule = rule + if self.url_prefix: + the_rule = state.url_prefix + rule + state.app.add_url_rule(the_rule, '%s.%s' % (self.name, endpoint), + view_func, **options) + self._record(register_rule) def before_request(self, f): + """Like :meth:`flask.Flask.before_request` but for a module""" self._record(lambda s: s.app.before_request_funcs - .setdefault(self.name, []).append(f), ()) + .setdefault(self.name, []).append(f)) return f def after_request(self, f): + """Like :meth:`flask.Flask.after_request` but for a module""" self._record(lambda s: s.app.after_request_funcs - .setdefault(self.name, []).append(f), ()) + .setdefault(self.name, []).append(f)) return f - def _record(self, func, args): - self._register_events.append((func, args)) - - def _register_rule(self, state, rule, endpoint, view_func, options): - if self.url_prefix: - rule = state.url_prefix + rule - state.app.add_url_rule(rule, '%s.%s' % (self.name, endpoint), - view_func, **options) + def _record(self, func): + self._register_events.append(func) -class Flask(object): +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 application. Once it is created it will act as a central registry for @@ -386,7 +432,9 @@ class Flask(object): extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] ) - def __init__(self, package_name): + def __init__(self, import_name): + _PackageBoundObject.__init__(self, import_name) + #: the debug flag. Set this to `True` to enable debugging of #: the application. In debug mode the debugger will kick in #: when an unhandled exception ocurrs and the integrated server @@ -394,13 +442,6 @@ class Flask(object): #: code are detected. self.debug = False - #: the name of the package or module. Do not change this once - #: it was set by the constructor. - self.package_name = package_name - - #: where is the app root located? - self.root_path = _get_package_path(self.package_name) - #: a dictionary of all view functions registered. The keys will #: be function names which are also used to generate URLs and #: the values are the function objects themselves. @@ -414,27 +455,31 @@ class Flask(object): #: decorator. self.error_handlers = {} - #: a list of functions that should be called at the beginning - #: of the request before request dispatching kicks in. This - #: can for example be used to open database connections or - #: getting hold of the currently logged in user. - #: To register a function here, use the :meth:`before_request` - #: decorator. + #: a dictionary with lists of functions that should be called at the + #: beginning of the request. The key of the dictionary is the name of + #: the module this function is active for, `None` for all requests. + #: This can for example be used to open database connections or + #: getting hold of the currently logged in user. To register a + #: function here, use the :meth:`before_request` decorator. self.before_request_funcs = {} - #: a list of functions that are called at the end of the - #: request. The function is passed the current response - #: object and modify it in place or replace it. - #: To register a function here use the :meth:`after_request` - #: decorator. + #: a dictionary with lists of functions that should be called after + #: each request. The key of the dictionary is the name of the module + #: this function is active for, `None` for all requests. This can for + #: example be used to open database connections or getting hold of the + #: currently logged in user. To register a function here, use the + #: :meth:`before_request` decorator. self.after_request_funcs = {} - #: a list of functions that are called without arguments - #: to populate the template context. Each returns a dictionary - #: that the template context is updated with. - #: To register a function here, use the :meth:`context_processor` - #: decorator. - self.template_context_processors = [_default_template_ctx_processor] + #: a dictionary with list of functions that are called without arguments + #: to populate the template context. They key of the dictionary is the + #: name of the module this function is active for, `None` for all + #: requests. Each returns a dictionary that the template context is + #: updated with. To register a function here, use the + #: :meth:`context_processor` decorator. + self.template_context_processors = { + None: [_default_template_ctx_processor] + } #: the :class:`~werkzeug.routing.Map` for this instance. You can use #: this to change the routing converters after the class was created @@ -457,7 +502,7 @@ class Flask(object): self.add_url_rule(self.static_path + '/', build_only=True, endpoint='static') if pkg_resources is not None: - target = (self.package_name, 'static') + target = (self.import_name, 'static') else: target = os.path.join(self.root_path, 'static') self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { @@ -483,7 +528,7 @@ class Flask(object): """ if pkg_resources is None: return FileSystemLoader(os.path.join(self.root_path, 'templates')) - return PackageLoader(self.package_name) + return PackageLoader(self.import_name) def update_template_context(self, context): """Update the template context with some commonly used variables. @@ -492,7 +537,11 @@ class Flask(object): :param context: the context as a dictionary that is updated in place to add extra variables. """ - for func in self.template_context_processors: + funcs = self.template_context_processors[None] + mod = _request_ctx_stack.top.request.module + if mod is not None and mod in self.template_context_processors: + funcs = chain(funcs, self.template_context_processors[mod]) + for func in funcs: context.update(func()) def run(self, host='127.0.0.1', port=5000, **options): @@ -521,32 +570,6 @@ class Flask(object): from werkzeug import Client return Client(self, self.response_class, use_cookies=True) - def open_resource(self, resource): - """Opens a resource from the application's resource folder. To see - how this works, consider the following folder structure:: - - /myapplication.py - /schemal.sql - /static - /style.css - /template - /layout.html - /index.html - - If you want to open the `schema.sql` file you would do the - following:: - - with app.open_resource('schema.sql') as f: - contents = f.read() - do_something_with(contents) - - :param resource: the name of the resource. To access resources within - subfolders use forward slashes as separator. - """ - if pkg_resources is None: - return open(os.path.join(self.root_path, resource), 'rb') - return pkg_resources.resource_stream(self.package_name, resource) - def open_session(self, request): """Creates or opens a new session. Default implementation stores all session data in a signed cookie. This requires that the @@ -574,8 +597,8 @@ class Flask(object): """Registers a module with this application.""" options.setdefault('url_prefix', module.url_prefix) state = _ModuleSetupState(self, **options) - for func, args in module._register_events: - func(state, *args) + for func in module._register_events: + func(state) def add_url_rule(self, rule, endpoint=None, view_func=None, **options): """Connects a URL rule. Works exactly like the :meth:`route` @@ -726,7 +749,7 @@ class Flask(object): def context_processor(self, f): """Registers a template context processor function.""" - self.template_context_processors.append(f) + self.template_context_processors[None].append(f) return f def dispatch_request(self): diff --git a/tests/flask_tests.py b/tests/flask_tests.py index e57b2f32..15b53651 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -302,7 +302,7 @@ class ModuleTestCase(unittest.TestCase): def test_basic_module(self): app = flask.Flask(__name__) - admin = flask.Module('admin', url_prefix='/admin') + admin = flask.Module(__name__, 'admin', url_prefix='/admin') @admin.route('/') def index(): return 'admin index' @@ -325,7 +325,7 @@ class ModuleTestCase(unittest.TestCase): def test_request_processing(self): catched = [] app = flask.Flask(__name__) - admin = flask.Module('admin', url_prefix='/admin') + admin = flask.Module(__name__, 'admin', url_prefix='/admin') @admin.before_request def before_admin_request(): catched.append('before-admin')