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
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 + '/<filename>',
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):

4
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')

Loading…
Cancel
Save