Permalink
Browse files

Latest iteration of the blueprint code. Far from being done

  • Loading branch information...
1 parent 673fa18 commit 7a08331ac062ee1e2ae3c7547f77c2d342ddb3df @mitsuhiko mitsuhiko committed May 29, 2011
Showing with 304 additions and 274 deletions.
  1. +42 −41 flask/app.py
  2. +172 −0 flask/blueprints.py
  3. +11 −0 flask/ctx.py
  4. +41 −21 flask/helpers.py
  5. +17 −204 flask/module.py
  6. +4 −1 flask/templating.py
  7. +16 −7 flask/wrappers.py
  8. +1 −0 tests/flask_tests.py
View
83 flask/app.py
@@ -28,7 +28,7 @@
from .ctx import RequestContext
from .globals import _request_ctx_stack, request
from .session import Session, _NullSession
-from .module import _ModuleSetupState
+from .module import blueprint_is_module
from .templating import DispatchingJinjaLoader, Environment, \
_default_template_ctx_processor
from .signals import request_started, request_finished, got_request_exception, \
@@ -103,14 +103,6 @@ class Flask(_PackageBoundObject):
#: :class:`~flask.Response` for more information.
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
#: application. In debug mode the debugger will kick in when an unhandled
#: exception ocurrs and the integrated server will automatically reload
@@ -213,10 +205,19 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.7
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)
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
#: exactly like a regular dictionary but supports additional methods
@@ -242,14 +243,14 @@ def __init__(self, import_name, static_path=None):
#: 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.
+ #: the blueprint 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 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
#: example be used to open database connections or getting hold of the
#: currently logged in user. To register a function here, use the
@@ -258,7 +259,7 @@ def __init__(self, import_name, static_path=None):
#: A dictionary with lists of functions that are called after
#: 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
#: the request, and their return values are ignored. If an exception
#: occurred while processing the request, it gets passed to each
@@ -270,19 +271,14 @@ def __init__(self, import_name, static_path=None):
#: A dictionary with list of functions that are called without argument
#: 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
#: updated with. To register a function here, use the
#: :meth:`context_processor` decorator.
self.template_context_processors = {
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
#: can be attached multiple times so this dictionary does not tell
#: you how often they got attached.
@@ -328,9 +324,10 @@ def __init__(self, import_name, static_path=None):
# while the server is running (usually happens during development)
# but also because google appengine stores static files somewhere
# else when mapped with the .yml file.
- self.add_url_rule(self.static_path + '/<path:filename>',
- endpoint='static',
- view_func=self.send_static_file)
+ if self.has_static_folder:
+ self.add_url_rule(self.static_url_path + '/<path:filename>',
+ endpoint='static',
+ view_func=self.send_static_file)
@property
def propagate_exceptions(self):
@@ -456,9 +453,9 @@ def update_template_context(self, context):
to add extra variables.
"""
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])
+ bp = _request_ctx_stack.top.request.blueprint
+ if bp is not None and bp in self.template_context_processors:
+ funcs = chain(funcs, self.template_context_processors[bp])
orig_ctx = context.copy()
for func in funcs:
context.update(func())
@@ -565,6 +562,8 @@ def register_module(self, module, **options):
The module system was deprecated in favor for the blueprint
system.
"""
+ assert blueprint_is_module(module), 'register_module requires ' \
+ 'actual module objects. Please upgrade to blueprints though.'
if not self.enable_modules:
raise RuntimeError('Module support was disabled but code '
'attempted to register a module named %r' % module)
@@ -577,12 +576,7 @@ def register_module(self, module, **options):
'of that extension instead. (Registered %r)' % module),
stacklevel=2)
- options.setdefault('url_prefix', module.url_prefix)
- options.setdefault('subdomain', module.subdomain)
- self.view_functions.update(module.view_functions)
- state = _ModuleSetupState(self, **options)
- for func in module._register_events:
- func(state)
+ self.register_blueprint(module, **options)
def register_blueprint(self, blueprint, **options):
"""Registers a blueprint on the application.
@@ -987,9 +981,9 @@ def preprocess_request(self):
request handling is stopped.
"""
funcs = self.before_request_funcs.get(None, ())
- mod = request.module
- if mod and mod in self.before_request_funcs:
- funcs = chain(funcs, self.before_request_funcs[mod])
+ bp = request.blueprint
+ if bp is not None and bp in self.before_request_funcs:
+ funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs:
rv = func()
if rv is not None:
@@ -1009,12 +1003,12 @@ def process_response(self, response):
instance of :attr:`response_class`.
"""
ctx = _request_ctx_stack.top
- mod = ctx.request.module
+ bp = ctx.request.blueprint
if not isinstance(ctx.session, _NullSession):
self.save_session(ctx.session, response)
funcs = ()
- if mod and mod in self.after_request_funcs:
- funcs = reversed(self.after_request_funcs[mod])
+ if bp is not None and bp in self.after_request_funcs:
+ funcs = reversed(self.after_request_funcs[bp])
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
@@ -1029,9 +1023,9 @@ def do_teardown_request(self):
tighter control over certain resources under testing environments.
"""
funcs = reversed(self.teardown_request_funcs.get(None, ()))
- mod = request.module
- if mod and mod in self.teardown_request_funcs:
- funcs = chain(funcs, reversed(self.teardown_request_funcs[mod]))
+ bp = request.blueprint
+ if bp is not None and bp in self.teardown_request_funcs:
+ funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
exc = sys.exc_info()[1]
for func in funcs:
rv = func(exc)
@@ -1120,6 +1114,13 @@ def wsgi_app(self, environ, start_response):
response = self.make_response(self.handle_exception(e))
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):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
View
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
View
11 flask/ctx.py
@@ -13,6 +13,7 @@
from .globals import _request_ctx_stack
from .session import _NullSession
+from .module import blueprint_is_module
class _RequestGlobals(object):
@@ -96,6 +97,16 @@ def __init__(self, app, environ):
except HTTPException, 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):
"""Binds the request context to the current context."""
_request_ctx_stack.push(self)
View
62 flask/helpers.py
@@ -156,19 +156,6 @@ def index():
def url_for(endpoint, **values):
"""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
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.
"""
ctx = _request_ctx_stack.top
- if '.' not in endpoint:
- mod = ctx.request.module
- if mod is not None:
- endpoint = mod + '.' + endpoint
- elif endpoint.startswith('.'):
- endpoint = endpoint[1:]
+ 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:
+ mod = ctx.request.blueprint
+ if mod is not None:
+ endpoint = mod + '.' + endpoint
+ elif endpoint.startswith('.'):
+ endpoint = endpoint[1:]
external = values.pop('_external', False)
return ctx.url_adapter.build(endpoint, values, force_external=external)
@@ -489,6 +481,8 @@ def __get__(self, obj, type=None):
class _PackageBoundObject(object):
+ template_folder = 'templates'
+
def __init__(self, import_name):
#: The name of the package or module. Do not change this once
#: it was set by the constructor.
@@ -497,29 +491,55 @@ def __init__(self, import_name):
#: Where is the app root located?
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
def has_static_folder(self):
"""This is `True` if the package bound object's container has a
folder named ``'static'``.
.. versionadded:: 0.5
"""
- return os.path.isdir(os.path.join(self.root_path, 'static'))
+ return self.static_folder is not None
@locked_cached_property
def jinja_loader(self):
"""The Jinja loader for this package bound object.
.. 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):
"""Function used internally to send static files from the static
folder to the browser.
.. 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'),
filename)
View
221 flask/module.py
@@ -9,109 +9,25 @@
:license: BSD, see LICENSE for more details.
"""
-from .helpers import _PackageBoundObject, _endpoint_from_view_func
-
-
-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::
-
- /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`)::
+import os
- from flask import Module
-
- admin = Module(__name__)
-
- @admin.route('/')
- def index():
- pass
+from .helpers import _PackageBoundObject, _endpoint_from_view_func
+from .blueprints import Blueprint
- @admin.route('/login')
- def login():
- pass
- For a gentle introduction into modules, checkout the
- :ref:`working-with-modules` section.
+def blueprint_is_module(bp):
+ """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
- The `subdomain` parameter was added.
+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.
- :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*.
+ .. versionchanged:: 0.7
+ Modules were deprecated in favor for blueprints.
"""
def __init__(self, import_name, name=None, url_prefix=None,
@@ -120,111 +36,8 @@ def __init__(self, import_name, name=None, url_prefix=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.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
+ Blueprint.__init__(self, name, import_name, url_prefix=url_prefix,
+ subdomain=subdomain)
- def _record(self, func):
- self._register_events.append(func)
+ if os.path.isdir(os.path.join(self.root_path, 'static')):
+ self._static_folder = 'static'
View
5 flask/templating.py
@@ -14,6 +14,7 @@
from .globals import _request_ctx_stack
from .signals import template_rendered
+from .module import blueprint_is_module
def _default_template_ctx_processor():
@@ -74,7 +75,9 @@ def get_source(self, environment, template):
loader = None
try:
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):
pass
try:
View
23 flask/wrappers.py
@@ -42,6 +42,10 @@ class Request(RequestBase):
#: something similar.
routing_exception = None
+ # switched by the request context until 1.0 to opt in deprecated
+ # module functionality
+ _is_old_module = False
+
@property
def max_content_length(self):
"""Read-only view of the `MAX_CONTENT_LENGTH` config key."""
@@ -61,17 +65,22 @@ def endpoint(self):
@property
def module(self):
- """The name of the current module"""
- if self.url_rule and \
- ':' not in self.url_rule.endpoint and \
- '.' in self.url_rule.endpoint:
- return self.url_rule.endpoint.rsplit('.', 1)[0]
+ """The name of the current module if the request was dispatched
+ to an actual module. This is deprecated functionality, use blueprints
+ instead.
+ """
+ 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
def blueprint(self):
"""The name of the current blueprint"""
- if self.url_rule and ':' in self.url_rule.endpoint:
- return self.url_rule.endpoint.split(':', 1)[0]
+ if self.url_rule and '.' in self.url_rule.endpoint:
+ return self.url_rule.endpoint.split('.', 1)[0]
@cached_property
def json(self):
View
1 tests/flask_tests.py
@@ -1192,6 +1192,7 @@ def test_endpoint_decorator(self):
from flask import Module
app = flask.Flask(__name__)
+ app.testing = True
app.url_map.add(Submount('/foo', [
Rule('/bar', endpoint='bar'),
Rule('/', endpoint='index')

0 comments on commit 7a08331

Please sign in to comment.