Browse Source

Modules are now instanciated with the package name.

This makes it possible to load resources from the folder the module is
located in.
pull/1638/head
Armin Ronacher 15 years ago
parent
commit
67f4b0f315
  1. 165
      flask.py
  2. 4
      tests/flask_tests.py

165
flask.py

@ -295,6 +295,43 @@ else:
_tojson_filter = json.dumps _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): class _ModuleSetupState(object):
def __init__(self, app, url_prefix=None): def __init__(self, app, url_prefix=None):
@ -302,44 +339,53 @@ class _ModuleSetupState(object):
self.url_prefix = url_prefix self.url_prefix = url_prefix
class Module(object): class Module(_PackageBoundObject):
"""Container object that enables pluggable applications""" """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.name = name
self.url_prefix = url_prefix self.url_prefix = url_prefix
self._register_events = [] self._register_events = []
def route(self, rule, **options): def route(self, rule, **options):
"""Like :meth:`flask.Flask.route` but for a module"""
def decorator(f): def decorator(f):
self.add_url_rule(rule, f.__name__, f, **options) self.add_url_rule(rule, f.__name__, f, **options)
return f return f
return decorator return decorator
def add_url_rule(self, rule, endpoint, view_func=None, **options): 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): def before_request(self, f):
"""Like :meth:`flask.Flask.before_request` but for a module"""
self._record(lambda s: s.app.before_request_funcs self._record(lambda s: s.app.before_request_funcs
.setdefault(self.name, []).append(f), ()) .setdefault(self.name, []).append(f))
return f return f
def after_request(self, 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 self._record(lambda s: s.app.after_request_funcs
.setdefault(self.name, []).append(f), ()) .setdefault(self.name, []).append(f))
return f return f
def _record(self, func, args): def _record(self, func):
self._register_events.append((func, args)) self._register_events.append(func)
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)
class Flask(object): 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
application. Once it is created it will act as a central registry for 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_'] 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 debug flag. Set this to `True` to enable debugging of
#: the application. In debug mode the debugger will kick in #: the application. In debug mode the debugger will kick in
#: when an unhandled exception ocurrs and the integrated server #: when an unhandled exception ocurrs and the integrated server
@ -394,13 +442,6 @@ class Flask(object):
#: code are detected. #: code are detected.
self.debug = False 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 #: a dictionary of all view functions registered. The keys will
#: be function names which are also used to generate URLs and #: be function names which are also used to generate URLs and
#: the values are the function objects themselves. #: the values are the function objects themselves.
@ -414,27 +455,31 @@ class Flask(object):
#: decorator. #: decorator.
self.error_handlers = {} self.error_handlers = {}
#: a list of functions that should be called at the beginning #: a dictionary with lists of functions that should be called at the
#: of the request before request dispatching kicks in. This #: beginning of the request. The key of the dictionary is the name of
#: can for example be used to open database connections or #: the module this function is active for, `None` for all requests.
#: getting hold of the currently logged in user. #: This can for example be used to open database connections or
#: To register a function here, use the :meth:`before_request` #: getting hold of the currently logged in user. To register a
#: decorator. #: function here, use the :meth:`before_request` decorator.
self.before_request_funcs = {} self.before_request_funcs = {}
#: a list of functions that are called at the end of the #: a dictionary with lists of functions that should be called after
#: request. The function is passed the current response #: each request. The key of the dictionary is the name of the module
#: object and modify it in place or replace it. #: this function is active for, `None` for all requests. This can for
#: To register a function here use the :meth:`after_request` #: example be used to open database connections or getting hold of the
#: decorator. #: currently logged in user. To register a function here, use the
#: :meth:`before_request` decorator.
self.after_request_funcs = {} self.after_request_funcs = {}
#: a list of functions that are called without arguments #: a dictionary with list of functions that are called without arguments
#: to populate the template context. Each returns a dictionary #: to populate the template context. They key of the dictionary is the
#: that the template context is updated with. #: name of the module this function is active for, `None` for all
#: To register a function here, use the :meth:`context_processor` #: requests. Each returns a dictionary that the template context is
#: decorator. #: updated with. To register a function here, use the
self.template_context_processors = [_default_template_ctx_processor] #: :meth:`context_processor` decorator.
self.template_context_processors = {
None: [_default_template_ctx_processor]
}
#: the :class:`~werkzeug.routing.Map` for this instance. You can use #: the :class:`~werkzeug.routing.Map` for this instance. You can use
#: this to change the routing converters after the class was created #: 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 + '/<filename>', self.add_url_rule(self.static_path + '/<filename>',
build_only=True, endpoint='static') build_only=True, endpoint='static')
if pkg_resources is not None: if pkg_resources is not None:
target = (self.package_name, 'static') target = (self.import_name, 'static')
else: else:
target = os.path.join(self.root_path, 'static') target = os.path.join(self.root_path, 'static')
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
@ -483,7 +528,7 @@ class Flask(object):
""" """
if pkg_resources is None: if pkg_resources is None:
return FileSystemLoader(os.path.join(self.root_path, 'templates')) 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): def update_template_context(self, context):
"""Update the template context with some commonly used variables. """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 :param context: the context as a dictionary that is updated in place
to add extra variables. 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()) context.update(func())
def run(self, host='127.0.0.1', port=5000, **options): def run(self, host='127.0.0.1', port=5000, **options):
@ -521,32 +570,6 @@ class Flask(object):
from werkzeug import Client from werkzeug import Client
return Client(self, self.response_class, use_cookies=True) 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): def open_session(self, request):
"""Creates or opens a new session. Default implementation stores all """Creates or opens a new session. Default implementation stores all
session data in a signed cookie. This requires that the session data in a signed cookie. This requires that the
@ -574,8 +597,8 @@ class Flask(object):
"""Registers a module with this application.""" """Registers a module with this application."""
options.setdefault('url_prefix', module.url_prefix) options.setdefault('url_prefix', module.url_prefix)
state = _ModuleSetupState(self, **options) state = _ModuleSetupState(self, **options)
for func, args in module._register_events: for func in module._register_events:
func(state, *args) func(state)
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`
@ -726,7 +749,7 @@ class Flask(object):
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.append(f) self.template_context_processors[None].append(f)
return f return f
def dispatch_request(self): def dispatch_request(self):

4
tests/flask_tests.py

@ -302,7 +302,7 @@ class ModuleTestCase(unittest.TestCase):
def test_basic_module(self): def test_basic_module(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
admin = flask.Module('admin', url_prefix='/admin') admin = flask.Module(__name__, 'admin', url_prefix='/admin')
@admin.route('/') @admin.route('/')
def index(): def index():
return 'admin index' return 'admin index'
@ -325,7 +325,7 @@ class ModuleTestCase(unittest.TestCase):
def test_request_processing(self): def test_request_processing(self):
catched = [] catched = []
app = flask.Flask(__name__) app = flask.Flask(__name__)
admin = flask.Module('admin', url_prefix='/admin') admin = flask.Module(__name__, 'admin', url_prefix='/admin')
@admin.before_request @admin.before_request
def before_admin_request(): def before_admin_request():
catched.append('before-admin') catched.append('before-admin')

Loading…
Cancel
Save