Latest iteration of the blueprint code. Far from being done
This commit is contained in:
parent
673fa18e6d
commit
7a08331ac0
8 changed files with 304 additions and 274 deletions
83
flask/app.py
83
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,9 +324,10 @@ 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:
|
||||||
endpoint='static',
|
self.add_url_rule(self.static_url_path + '/<path:filename>',
|
||||||
view_func=self.send_static_file)
|
endpoint='static',
|
||||||
|
view_func=self.send_static_file)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def propagate_exceptions(self):
|
def propagate_exceptions(self):
|
||||||
|
|
@ -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
Normal file
172
flask/blueprints.py
Normal file
|
|
@ -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
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)
|
||||||
|
|
|
||||||
|
|
@ -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,12 +168,17 @@ 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 in endpoint:
|
if not ctx.request._is_old_module:
|
||||||
mod = ctx.request.module
|
if endpoint[:1] == '.':
|
||||||
if mod is not None:
|
endpoint = request.blueprint + endpoint
|
||||||
endpoint = mod + '.' + endpoint
|
else:
|
||||||
elif endpoint.startswith('.'):
|
# TODO: get rid of this deprecated functionality in 1.0
|
||||||
endpoint = endpoint[1:]
|
if '.' not in endpoint:
|
||||||
|
mod = ctx.request.blueprint
|
||||||
|
if mod is not None:
|
||||||
|
endpoint = mod + '.' + endpoint
|
||||||
|
elif endpoint.startswith('.'):
|
||||||
|
endpoint = endpoint[1:]
|
||||||
external = values.pop('_external', False)
|
external = values.pop('_external', False)
|
||||||
return ctx.url_adapter.build(endpoint, values, force_external=external)
|
return ctx.url_adapter.build(endpoint, values, force_external=external)
|
||||||
|
|
||||||
|
|
@ -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
221
flask/module.py
|
|
@ -9,109 +9,25 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, _endpoint_from_view_func
|
from .helpers import _PackageBoundObject, _endpoint_from_view_func
|
||||||
|
from .blueprints import Blueprint
|
||||||
|
|
||||||
|
|
||||||
def _register_module(module, static_path):
|
def blueprint_is_module(bp):
|
||||||
"""Internal helper function that returns a function for recording
|
"""Used to figure out if something is actually a module"""
|
||||||
that registers the `send_static_file` function for the module on
|
return isinstance(bp, Module)
|
||||||
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):
|
class Module(Blueprint):
|
||||||
|
"""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.
|
||||||
|
|
||||||
def __init__(self, app, url_prefix=None, subdomain=None):
|
.. versionchanged:: 0.7
|
||||||
self.app = app
|
Modules were deprecated in favor for blueprints.
|
||||||
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::
|
|
||||||
|
|
||||||
/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
|
|
||||||
:ref:`working-with-modules` section.
|
|
||||||
|
|
||||||
.. 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
|
|
||||||
The `subdomain` parameter was added.
|
|
||||||
|
|
||||||
:param import_name: the name of the Python package or module
|
|
||||||
implementing this :class:`Module`.
|
|
||||||
: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):
|
if os.path.isdir(os.path.join(self.root_path, 'static')):
|
||||||
"""Like :meth:`Flask.route` but for a module. The endpoint for the
|
self._static_folder = 'static'
|
||||||
: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):
|
|
||||||
self._register_events.append(func)
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue