Browse Source

Latest iteration of the blueprint code. Far from being done

pull/262/head
Armin Ronacher 14 years ago
parent
commit
7a08331ac0
  1. 79
      flask/app.py
  2. 172
      flask/blueprints.py
  3. 11
      flask/ctx.py
  4. 52
      flask/helpers.py
  5. 221
      flask/module.py
  6. 5
      flask/templating.py
  7. 23
      flask/wrappers.py
  8. 1
      tests/flask_tests.py

79
flask/app.py

@ -28,7 +28,7 @@ from .config import ConfigAttribute, Config
from .ctx import RequestContext from .ctx import RequestContext
from .globals import _request_ctx_stack, request from .globals import _request_ctx_stack, request
from .session import Session, _NullSession from .session import Session, _NullSession
from .module import _ModuleSetupState from .module import blueprint_is_module
from .templating import DispatchingJinjaLoader, Environment, \ from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor _default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception, \ from .signals import request_started, request_finished, got_request_exception, \
@ -103,14 +103,6 @@ class Flask(_PackageBoundObject):
#: :class:`~flask.Response` for more information. #: :class:`~flask.Response` for more information.
response_class = Response response_class = Response
#: Path for the static files. If you don't want to use static files
#: you can set this value to `None` in which case no URL rule is added
#: and the development server will no longer serve any static files.
#:
#: This is the default used for application and modules unless a
#: different value is passed to the constructor.
static_path = '/static'
#: The debug flag. Set this to `True` to enable debugging of the #: The debug flag. Set this to `True` to enable debugging of the
#: application. In debug mode the debugger will kick in when an unhandled #: application. In debug mode the debugger will kick in when an unhandled
#: exception ocurrs and the integrated server will automatically reload #: exception ocurrs and the integrated server will automatically reload
@ -213,10 +205,19 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.7 #: .. versionadded:: 0.7
test_client_class = None test_client_class = None
def __init__(self, import_name, static_path=None): def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static'):
_PackageBoundObject.__init__(self, import_name) _PackageBoundObject.__init__(self, import_name)
if static_path is not None: if static_path is not None:
self.static_path = static_path from warnings import warn
warn(DeprecationWarning('static_path is now called '
'static_url_path'), stacklevel=2)
static_url_path = static_path
if static_url_path is not None:
self.static_url_path = static_url_path
if static_folder is not None:
self.static_folder = static_folder
#: The configuration dictionary as :class:`Config`. This behaves #: The configuration dictionary as :class:`Config`. This behaves
#: exactly like a regular dictionary but supports additional methods #: exactly like a regular dictionary but supports additional methods
@ -242,14 +243,14 @@ class Flask(_PackageBoundObject):
#: A dictionary with lists of functions that should be called at the #: 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 #: beginning of the request. The key of the dictionary is the name of
#: the module this function is active for, `None` for all requests. #: the blueprint this function is active for, `None` for all requests.
#: This can for example be used to open database connections or #: This can for example be used to open database connections or
#: getting hold of the currently logged in user. To register a #: getting hold of the currently logged in user. To register a
#: function here, use the :meth:`before_request` decorator. #: function here, use the :meth:`before_request` decorator.
self.before_request_funcs = {} self.before_request_funcs = {}
#: A dictionary with lists of functions that should be called after #: A dictionary with lists of functions that should be called after
#: each request. The key of the dictionary is the name of the module #: 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 #: this function is active for, `None` for all requests. This can for
#: example be used to open database connections or getting hold of the #: example be used to open database connections or getting hold of the
#: currently logged in user. To register a function here, use the #: currently logged in user. To register a function here, use the
@ -258,7 +259,7 @@ class Flask(_PackageBoundObject):
#: A dictionary with lists of functions that are called after #: A dictionary with lists of functions that are called after
#: each request, even if an exception has occurred. The key of the #: each request, even if an exception has occurred. The key of the
#: dictionary is the name of the module this function is active for, #: dictionary is the name of the blueprint this function is active for,
#: `None` for all requests. These functions are not allowed to modify #: `None` for all requests. These functions are not allowed to modify
#: the request, and their return values are ignored. If an exception #: the request, and their return values are ignored. If an exception
#: occurred while processing the request, it gets passed to each #: occurred while processing the request, it gets passed to each
@ -270,7 +271,7 @@ class Flask(_PackageBoundObject):
#: A dictionary with list of functions that are called without argument #: A dictionary with list of functions that are called without argument
#: to populate the template context. The key of the dictionary is the #: to populate the template context. The key of the dictionary is the
#: name of the module this function is active for, `None` for all #: name of the blueprint this function is active for, `None` for all
#: requests. Each returns a dictionary that the template context is #: requests. Each returns a dictionary that the template context is
#: updated with. To register a function here, use the #: updated with. To register a function here, use the
#: :meth:`context_processor` decorator. #: :meth:`context_processor` decorator.
@ -278,11 +279,6 @@ class Flask(_PackageBoundObject):
None: [_default_template_ctx_processor] None: [_default_template_ctx_processor]
} }
#: all the loaded modules in a dictionary by name.
#:
#: .. versionadded:: 0.5
self.modules = {}
#: all the attached blueprints in a directory by name. Blueprints #: all the attached blueprints in a directory by name. Blueprints
#: can be attached multiple times so this dictionary does not tell #: can be attached multiple times so this dictionary does not tell
#: you how often they got attached. #: you how often they got attached.
@ -328,7 +324,8 @@ class Flask(_PackageBoundObject):
# while the server is running (usually happens during development) # while the server is running (usually happens during development)
# but also because google appengine stores static files somewhere # but also because google appengine stores static files somewhere
# else when mapped with the .yml file. # else when mapped with the .yml file.
self.add_url_rule(self.static_path + '/<path:filename>', if self.has_static_folder:
self.add_url_rule(self.static_url_path + '/<path:filename>',
endpoint='static', endpoint='static',
view_func=self.send_static_file) view_func=self.send_static_file)
@ -456,9 +453,9 @@ class Flask(_PackageBoundObject):
to add extra variables. to add extra variables.
""" """
funcs = self.template_context_processors[None] funcs = self.template_context_processors[None]
mod = _request_ctx_stack.top.request.module bp = _request_ctx_stack.top.request.blueprint
if mod is not None and mod in self.template_context_processors: if bp is not None and bp in self.template_context_processors:
funcs = chain(funcs, self.template_context_processors[mod]) funcs = chain(funcs, self.template_context_processors[bp])
orig_ctx = context.copy() orig_ctx = context.copy()
for func in funcs: for func in funcs:
context.update(func()) context.update(func())
@ -565,6 +562,8 @@ class Flask(_PackageBoundObject):
The module system was deprecated in favor for the blueprint The module system was deprecated in favor for the blueprint
system. system.
""" """
assert blueprint_is_module(module), 'register_module requires ' \
'actual module objects. Please upgrade to blueprints though.'
if not self.enable_modules: if not self.enable_modules:
raise RuntimeError('Module support was disabled but code ' raise RuntimeError('Module support was disabled but code '
'attempted to register a module named %r' % module) 'attempted to register a module named %r' % module)
@ -577,12 +576,7 @@ class Flask(_PackageBoundObject):
'of that extension instead. (Registered %r)' % module), 'of that extension instead. (Registered %r)' % module),
stacklevel=2) stacklevel=2)
options.setdefault('url_prefix', module.url_prefix) self.register_blueprint(module, **options)
options.setdefault('subdomain', module.subdomain)
self.view_functions.update(module.view_functions)
state = _ModuleSetupState(self, **options)
for func in module._register_events:
func(state)
def register_blueprint(self, blueprint, **options): def register_blueprint(self, blueprint, **options):
"""Registers a blueprint on the application. """Registers a blueprint on the application.
@ -987,9 +981,9 @@ class Flask(_PackageBoundObject):
request handling is stopped. request handling is stopped.
""" """
funcs = self.before_request_funcs.get(None, ()) funcs = self.before_request_funcs.get(None, ())
mod = request.module bp = request.blueprint
if mod and mod in self.before_request_funcs: if bp is not None and bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[mod]) funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs: for func in funcs:
rv = func() rv = func()
if rv is not None: if rv is not None:
@ -1009,12 +1003,12 @@ class Flask(_PackageBoundObject):
instance of :attr:`response_class`. instance of :attr:`response_class`.
""" """
ctx = _request_ctx_stack.top ctx = _request_ctx_stack.top
mod = ctx.request.module bp = ctx.request.blueprint
if not isinstance(ctx.session, _NullSession): if not isinstance(ctx.session, _NullSession):
self.save_session(ctx.session, response) self.save_session(ctx.session, response)
funcs = () funcs = ()
if mod and mod in self.after_request_funcs: if bp is not None and bp in self.after_request_funcs:
funcs = reversed(self.after_request_funcs[mod]) funcs = reversed(self.after_request_funcs[bp])
if None in self.after_request_funcs: if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None])) funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs: for handler in funcs:
@ -1029,9 +1023,9 @@ class Flask(_PackageBoundObject):
tighter control over certain resources under testing environments. tighter control over certain resources under testing environments.
""" """
funcs = reversed(self.teardown_request_funcs.get(None, ())) funcs = reversed(self.teardown_request_funcs.get(None, ()))
mod = request.module bp = request.blueprint
if mod and mod in self.teardown_request_funcs: if bp is not None and bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[mod])) funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
exc = sys.exc_info()[1] exc = sys.exc_info()[1]
for func in funcs: for func in funcs:
rv = func(exc) rv = func(exc)
@ -1120,6 +1114,13 @@ class Flask(_PackageBoundObject):
response = self.make_response(self.handle_exception(e)) response = self.make_response(self.handle_exception(e))
return response(environ, start_response) return response(environ, start_response)
@property
def modules(self):
from warnings import warn
warn(DeprecationWarning('Flask.modules is deprecated, use '
'Flask.blueprints instead'), stacklevel=2)
return self.blueprints
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`.""" """Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response) return self.wsgi_app(environ, start_response)

172
flask/blueprints.py

@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
"""
flask.blueprints
~~~~~~~~~~~~~~~~
Blueprints are the recommended way to implement larger or more
pluggable applications in Flask 0.7 and later.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import os
from .helpers import _PackageBoundObject, _endpoint_from_view_func
class BlueprintSetupState(object):
"""Temporary holder object for registering a blueprint with the
application.
"""
def __init__(self, blueprint, app, options):
self.app = app
self.blueprint = blueprint
self.options = options
subdomain = self.options.get('subdomain')
if subdomain is None:
subdomain = self.blueprint.subdomain
self.subdomain = subdomain
url_prefix = self.options.get('url_prefix')
if url_prefix is None:
url_prefix = self.blueprint.url_prefix
self.url_prefix = url_prefix
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if self.url_prefix:
rule = self.url_prefix + rule
options.setdefault('subdomain', self.subdomain)
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
view_func, **options)
class Blueprint(_PackageBoundObject):
"""Represents a blueprint.
.. versionadded:: 0.7
"""
def __init__(self, name, import_name, static_folder=None,
static_url_path=None, url_prefix=None,
subdomain=None):
_PackageBoundObject.__init__(self, import_name)
self.name = name
self.url_prefix = url_prefix
self.subdomain = subdomain
self.static_folder = static_folder
self.static_url_path = static_url_path
self.deferred_functions = []
def _record(self, func):
self.deferred_functions.append(func)
def make_setup_state(self, app, options):
return BlueprintSetupState(self, app, options)
def register(self, app, options):
"""Called by :meth:`Flask.register_blueprint` to register a blueprint
on the application. This can be overridden to customize the register
behavior. Keyword arguments from
:func:`~flask.Flask.register_blueprint` are directly forwarded to this
method in the `options` dictionary.
"""
state = self.make_setup_state(app, options)
if self.has_static_folder:
state.add_url_rule(self.static_url_path + '/<path:filename>',
view_func=self.send_static_file,
endpoint='static')
for deferred in self.deferred_functions:
deferred(state)
def route(self, rule, **options):
"""Like :meth:`Flask.route` but for a module. The endpoint for the
:func:`url_for` function is prefixed with the name of the module.
"""
def decorator(f):
self.add_url_rule(rule, f.__name__, f, **options)
return f
return decorator
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
"""Like :meth:`Flask.add_url_rule` but for a module. The endpoint for
the :func:`url_for` function is prefixed with the name of the module.
"""
def register_rule(state):
state.add_url_rule(rule, endpoint, view_func, **options)
self._record(register_rule)
def endpoint(self, endpoint):
"""Like :meth:`Flask.endpoint` but for a module. This does not
prefix the endpoint with the module name, this has to be done
explicitly by the user of this method.
"""
def decorator(f):
def register_endpoint(state):
state.app.view_functions[endpoint] = f
self._record(register_endpoint)
return f
return decorator
def before_request(self, f):
"""Like :meth:`Flask.before_request` but for a module. This function
is only executed before each request that is handled by a function of
that module.
"""
self._record(lambda s: s.app.before_request_funcs
.setdefault(self.name, []).append(f))
return f
def before_app_request(self, f):
"""Like :meth:`Flask.before_request`. Such a function is executed
before each request, even if outside of a module.
"""
self._record(lambda s: s.app.before_request_funcs
.setdefault(None, []).append(f))
return f
def after_request(self, f):
"""Like :meth:`Flask.after_request` but for a module. This function
is only executed after each request that is handled by a function of
that module.
"""
self._record(lambda s: s.app.after_request_funcs
.setdefault(self.name, []).append(f))
return f
def after_app_request(self, f):
"""Like :meth:`Flask.after_request` but for a module. Such a function
is executed after each request, even if outside of the module.
"""
self._record(lambda s: s.app.after_request_funcs
.setdefault(None, []).append(f))
return f
def context_processor(self, f):
"""Like :meth:`Flask.context_processor` but for a module. This
function is only executed for requests handled by a module.
"""
self._record(lambda s: s.app.template_context_processors
.setdefault(self.name, []).append(f))
return f
def app_context_processor(self, f):
"""Like :meth:`Flask.context_processor` but for a module. Such a
function is executed each request, even if outside of the module.
"""
self._record(lambda s: s.app.template_context_processors
.setdefault(None, []).append(f))
return f
def app_errorhandler(self, code):
"""Like :meth:`Flask.errorhandler` but for a module. This
handler is used for all requests, even if outside of the module.
"""
def decorator(f):
self._record(lambda s: s.app.errorhandler(code)(f))
return f
return decorator

11
flask/ctx.py

@ -13,6 +13,7 @@ from werkzeug.exceptions import HTTPException
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
from .session import _NullSession from .session import _NullSession
from .module import blueprint_is_module
class _RequestGlobals(object): class _RequestGlobals(object):
@ -96,6 +97,16 @@ class RequestContext(object):
except HTTPException, e: except HTTPException, e:
self.request.routing_exception = e self.request.routing_exception = e
# Support for deprecated functionality. This is doing away with
# Flask 1.0
blueprint = self.request.blueprint
if blueprint is not None:
# better safe than sorry, we don't want to break code that
# already worked
bp = app.blueprints.get(blueprint)
if bp is not None and blueprint_is_module(bp):
self.request._is_old_module = True
def push(self): def push(self):
"""Binds the request context to the current context.""" """Binds the request context to the current context."""
_request_ctx_stack.push(self) _request_ctx_stack.push(self)

52
flask/helpers.py

@ -156,19 +156,6 @@ def make_response(*args):
def url_for(endpoint, **values): def url_for(endpoint, **values):
"""Generates a URL to the given endpoint with the method provided. """Generates a URL to the given endpoint with the method provided.
The endpoint is relative to the active module if modules are in use.
Here are some examples:
==================== ======================= =============================
Active Module Target Endpoint Target Function
==================== ======================= =============================
`None` ``'index'`` `index` of the application
`None` ``'.index'`` `index` of the application
``'admin'`` ``'index'`` `index` of the `admin` module
any ``'.index'`` `index` of the application
any ``'admin.index'`` `index` of the `admin` module
==================== ======================= =============================
Variable arguments that are unknown to the target endpoint are appended Variable arguments that are unknown to the target endpoint are appended
to the generated URL as query arguments. If the value of a query argument to the generated URL as query arguments. If the value of a query argument
@ -181,8 +168,13 @@ def url_for(endpoint, **values):
:param _external: if set to `True`, an absolute URL is generated. :param _external: if set to `True`, an absolute URL is generated.
""" """
ctx = _request_ctx_stack.top ctx = _request_ctx_stack.top
if not ctx.request._is_old_module:
if endpoint[:1] == '.':
endpoint = request.blueprint + endpoint
else:
# TODO: get rid of this deprecated functionality in 1.0
if '.' not in endpoint: if '.' not in endpoint:
mod = ctx.request.module mod = ctx.request.blueprint
if mod is not None: if mod is not None:
endpoint = mod + '.' + endpoint endpoint = mod + '.' + endpoint
elif endpoint.startswith('.'): elif endpoint.startswith('.'):
@ -489,6 +481,8 @@ class locked_cached_property(object):
class _PackageBoundObject(object): class _PackageBoundObject(object):
template_folder = 'templates'
def __init__(self, import_name): def __init__(self, import_name):
#: The name of the package or module. Do not change this once #: The name of the package or module. Do not change this once
#: it was set by the constructor. #: it was set by the constructor.
@ -497,6 +491,28 @@ class _PackageBoundObject(object):
#: Where is the app root located? #: Where is the app root located?
self.root_path = _get_package_path(self.import_name) self.root_path = _get_package_path(self.import_name)
self._static_folder = None
self._static_url_path = None
def _get_static_folder(self):
if self._static_folder is not None:
return os.path.join(self.root_path, self._static_folder)
def _set_static_folder(self, value):
self._static_folder = value
static_folder = property(_get_static_folder, _set_static_folder)
del _get_static_folder, _set_static_folder
def _get_static_url_path(self):
if self._static_url_path is None:
if self.static_folder is None:
return None
return '/' + os.path.basename(self.static_folder)
return self._static_url_path
def _set_static_url_path(self, value):
self._static_url_path = value
static_url_path = property(_get_static_url_path, _set_static_url_path)
del _get_static_url_path, _set_static_url_path
@property @property
def has_static_folder(self): def has_static_folder(self):
"""This is `True` if the package bound object's container has a """This is `True` if the package bound object's container has a
@ -504,7 +520,7 @@ class _PackageBoundObject(object):
.. versionadded:: 0.5 .. versionadded:: 0.5
""" """
return os.path.isdir(os.path.join(self.root_path, 'static')) return self.static_folder is not None
@locked_cached_property @locked_cached_property
def jinja_loader(self): def jinja_loader(self):
@ -512,7 +528,9 @@ class _PackageBoundObject(object):
.. versionadded:: 0.5 .. versionadded:: 0.5
""" """
return FileSystemLoader(os.path.join(self.root_path, 'templates')) if self.template_folder is not None:
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
def send_static_file(self, filename): def send_static_file(self, filename):
"""Function used internally to send static files from the static """Function used internally to send static files from the static
@ -520,6 +538,8 @@ class _PackageBoundObject(object):
.. versionadded:: 0.5 .. versionadded:: 0.5
""" """
if not self.has_static_folder:
raise RuntimeError('No static folder for this object')
return send_from_directory(os.path.join(self.root_path, 'static'), return send_from_directory(os.path.join(self.root_path, 'static'),
filename) filename)

221
flask/module.py

@ -9,109 +9,25 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from .helpers import _PackageBoundObject, _endpoint_from_view_func import os
def _register_module(module, static_path):
"""Internal helper function that returns a function for recording
that registers the `send_static_file` function for the module on
the application if necessary. It also registers the module on
the application.
"""
def _register(state):
state.app.modules[module.name] = module
# do not register the rule if the static folder of the
# module is the same as the one from the application.
if state.app.root_path == module.root_path:
return
path = static_path
if path is None:
path = state.app.static_path
if state.url_prefix:
path = state.url_prefix + path
state.app.add_url_rule(path + '/<path:filename>',
endpoint='%s.static' % module.name,
view_func=module.send_static_file,
subdomain=state.subdomain)
return _register
class _ModuleSetupState(object):
def __init__(self, app, url_prefix=None, subdomain=None):
self.app = app
self.url_prefix = url_prefix
self.subdomain = subdomain
class Module(_PackageBoundObject):
"""Container object that enables pluggable applications. A module can
be used to organize larger applications. They represent blueprints that,
in combination with a :class:`Flask` object are used to create a large
application.
A module is like an application bound to an `import_name`. Multiple
modules can share the same import names, but in that case a `name` has
to be provided to keep them apart. If different import names are used,
the rightmost part of the import name is used as name.
Here's an example structure for a larger application:: from .helpers import _PackageBoundObject, _endpoint_from_view_func
from .blueprints import Blueprint
/myapplication
/__init__.py
/views
/__init__.py
/admin.py
/frontend.py
The `myapplication/__init__.py` can look like this::
from flask import Flask
from myapplication.views.admin import admin
from myapplication.views.frontend import frontend
app = Flask(__name__)
app.register_module(admin, url_prefix='/admin')
app.register_module(frontend)
And here's an example view module (`myapplication/views/admin.py`)::
from flask import Module
admin = Module(__name__)
@admin.route('/')
def index():
pass
@admin.route('/login')
def login():
pass
For a gentle introduction into modules, checkout the def blueprint_is_module(bp):
:ref:`working-with-modules` section. """Used to figure out if something is actually a module"""
return isinstance(bp, Module)
.. versionadded:: 0.5
The `static_path` parameter was added and it's now possible for
modules to refer to their own templates and static files. See
:ref:`modules-and-resources` for more information.
.. versionadded:: 0.6 class Module(Blueprint):
The `subdomain` parameter was added. """Deprecated module support. Until Flask 0.6 modules were a different
name of the concept now available as blueprints in Flask. They are
essentially doing the same but have some bad semantics for templates and
static files that were fixed with blueprints.
:param import_name: the name of the Python package or module .. versionchanged:: 0.7
implementing this :class:`Module`. Modules were deprecated in favor for blueprints.
:param name: the internal short name for the module. Unless specified
the rightmost part of the import name
:param url_prefix: an optional string that is used to prefix all the
URL rules of this module. This can also be specified
when registering the module with the application.
:param subdomain: used to set the subdomain setting for URL rules that
do not have a subdomain setting set.
:param static_path: can be used to specify a different path for the
static files on the web. Defaults to ``/static``.
This does not affect the folder the files are served
*from*.
""" """
def __init__(self, import_name, name=None, url_prefix=None, def __init__(self, import_name, name=None, url_prefix=None,
@ -120,111 +36,8 @@ class Module(_PackageBoundObject):
assert '.' in import_name, 'name required if package name ' \ assert '.' in import_name, 'name required if package name ' \
'does not point to a submodule' 'does not point to a submodule'
name = import_name.rsplit('.', 1)[1] name = import_name.rsplit('.', 1)[1]
_PackageBoundObject.__init__(self, import_name) Blueprint.__init__(self, name, import_name, url_prefix=url_prefix,
self.name = name subdomain=subdomain)
self.url_prefix = url_prefix
self.subdomain = subdomain
self.view_functions = {}
self._register_events = [_register_module(self, static_path)]
def route(self, rule, **options):
"""Like :meth:`Flask.route` but for a module. The endpoint for the
:func:`url_for` function is prefixed with the name of the module.
"""
def decorator(f):
self.add_url_rule(rule, f.__name__, f, **options)
return f
return decorator
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
"""Like :meth:`Flask.add_url_rule` but for a module. The endpoint for
the :func:`url_for` function is prefixed with the name of the module.
.. versionchanged:: 0.6
The `endpoint` argument is now optional and will default to the
function name to consistent with the function of the same name
on the application object.
"""
def register_rule(state):
the_rule = rule
if state.url_prefix:
the_rule = state.url_prefix + rule
options.setdefault('subdomain', state.subdomain)
the_endpoint = endpoint
if the_endpoint is None:
the_endpoint = _endpoint_from_view_func(view_func)
state.app.add_url_rule(the_rule, '%s.%s' % (self.name,
the_endpoint),
view_func, **options)
self._record(register_rule)
def endpoint(self, endpoint):
"""Like :meth:`Flask.endpoint` but for a module."""
def decorator(f):
self.view_functions[endpoint] = f
return f
return decorator
def before_request(self, f):
"""Like :meth:`Flask.before_request` but for a module. This function
is only executed before each request that is handled by a function of
that module.
"""
self._record(lambda s: s.app.before_request_funcs
.setdefault(self.name, []).append(f))
return f
def before_app_request(self, f):
"""Like :meth:`Flask.before_request`. Such a function is executed
before each request, even if outside of a module.
"""
self._record(lambda s: s.app.before_request_funcs
.setdefault(None, []).append(f))
return f
def after_request(self, f):
"""Like :meth:`Flask.after_request` but for a module. This function
is only executed after each request that is handled by a function of
that module.
"""
self._record(lambda s: s.app.after_request_funcs
.setdefault(self.name, []).append(f))
return f
def after_app_request(self, f):
"""Like :meth:`Flask.after_request` but for a module. Such a function
is executed after each request, even if outside of the module.
"""
self._record(lambda s: s.app.after_request_funcs
.setdefault(None, []).append(f))
return f
def context_processor(self, f):
"""Like :meth:`Flask.context_processor` but for a module. This
function is only executed for requests handled by a module.
"""
self._record(lambda s: s.app.template_context_processors
.setdefault(self.name, []).append(f))
return f
def app_context_processor(self, f):
"""Like :meth:`Flask.context_processor` but for a module. Such a
function is executed each request, even if outside of the module.
"""
self._record(lambda s: s.app.template_context_processors
.setdefault(None, []).append(f))
return f
def app_errorhandler(self, code):
"""Like :meth:`Flask.errorhandler` but for a module. This
handler is used for all requests, even if outside of the module.
.. versionadded:: 0.4
"""
def decorator(f):
self._record(lambda s: s.app.errorhandler(code)(f))
return f
return decorator
def _record(self, func): if os.path.isdir(os.path.join(self.root_path, 'static')):
self._register_events.append(func) self._static_folder = 'static'

5
flask/templating.py

@ -14,6 +14,7 @@ from jinja2 import BaseLoader, Environment as BaseEnvironment, \
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
from .signals import template_rendered from .signals import template_rendered
from .module import blueprint_is_module
def _default_template_ctx_processor(): def _default_template_ctx_processor():
@ -74,7 +75,9 @@ class DispatchingJinjaLoader(BaseLoader):
loader = None loader = None
try: try:
module, name = posixpath.normpath(template).split('/', 1) module, name = posixpath.normpath(template).split('/', 1)
loader = self.app.modules[module].jinja_loader blueprint = self.app.blueprints[module]
if blueprint_is_module(blueprint):
loader = blueprint.jinja_loader
except (ValueError, KeyError, TemplateNotFound): except (ValueError, KeyError, TemplateNotFound):
pass pass
try: try:

23
flask/wrappers.py

@ -42,6 +42,10 @@ class Request(RequestBase):
#: something similar. #: something similar.
routing_exception = None routing_exception = None
# switched by the request context until 1.0 to opt in deprecated
# module functionality
_is_old_module = False
@property @property
def max_content_length(self): def max_content_length(self):
"""Read-only view of the `MAX_CONTENT_LENGTH` config key.""" """Read-only view of the `MAX_CONTENT_LENGTH` config key."""
@ -61,17 +65,22 @@ class Request(RequestBase):
@property @property
def module(self): def module(self):
"""The name of the current module""" """The name of the current module if the request was dispatched
if self.url_rule and \ to an actual module. This is deprecated functionality, use blueprints
':' not in self.url_rule.endpoint and \ instead.
'.' in self.url_rule.endpoint: """
return self.url_rule.endpoint.rsplit('.', 1)[0] from warnings import warn
warn(DeprecationWarning('modules were deprecated in favor of '
'blueprints. Use request.blueprint '
'instead.'), stacklevel=2)
if self._is_old_module:
return self.blueprint
@property @property
def blueprint(self): def blueprint(self):
"""The name of the current blueprint""" """The name of the current blueprint"""
if self.url_rule and ':' in self.url_rule.endpoint: if self.url_rule and '.' in self.url_rule.endpoint:
return self.url_rule.endpoint.split(':', 1)[0] return self.url_rule.endpoint.split('.', 1)[0]
@cached_property @cached_property
def json(self): def json(self):

1
tests/flask_tests.py

@ -1192,6 +1192,7 @@ class ModuleTestCase(unittest.TestCase):
from flask import Module from flask import Module
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.testing = True
app.url_map.add(Submount('/foo', [ app.url_map.add(Submount('/foo', [
Rule('/bar', endpoint='bar'), Rule('/bar', endpoint='bar'),
Rule('/', endpoint='index') Rule('/', endpoint='index')

Loading…
Cancel
Save