extract common Flask/Blueprint API to Scaffold base class

Co-authored-by: Chris Nguyen <chrisngyn99@gmail.com>
This commit is contained in:
yk396 2020-07-22 15:02:31 -04:00 committed by David Lord
parent 216d97c21a
commit b146a13f63
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
5 changed files with 473 additions and 495 deletions

View file

@ -1,10 +1,8 @@
from functools import update_wrapper
from .helpers import _endpoint_from_view_func
from .helpers import _PackageBoundObject
# a singleton sentinel value for parameter defaults
_sentinel = object()
from .scaffold import _endpoint_from_view_func
from .scaffold import _sentinel
from .scaffold import Scaffold
class BlueprintSetupState:
@ -76,7 +74,7 @@ class BlueprintSetupState:
)
class Blueprint(_PackageBoundObject):
class Blueprint(Scaffold):
"""Represents a blueprint, a collection of routes and other
app-related functions that can be registered on a real application
later.
@ -167,20 +165,25 @@ class Blueprint(_PackageBoundObject):
root_path=None,
cli_group=_sentinel,
):
_PackageBoundObject.__init__(
self, import_name, template_folder, root_path=root_path
super().__init__(
import_name=import_name,
static_folder=static_folder,
static_url_path=static_url_path,
template_folder=template_folder,
root_path=root_path,
)
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 = []
if url_defaults is None:
url_defaults = {}
self.url_values_defaults = url_defaults
self.cli_group = cli_group
def _is_setup_finished(self):
return self.warn_on_modifications and self._got_registered_once
def record(self, func):
"""Registers a function that is called when the blueprint is
registered on the application. This function is called with the
@ -241,6 +244,36 @@ class Blueprint(_PackageBoundObject):
endpoint="static",
)
# Merge app and self dictionaries.
def merge_dict_lists(self_dict, app_dict):
"""Merges self_dict into app_dict. Replaces None keys with self.name.
Values of dict must be lists.
"""
for key, values in self_dict.items():
key = self.name if key is None else f"{self.name}.{key}"
app_dict.setdefault(key, []).extend(values)
def merge_dict_nested(self_dict, app_dict):
"""Merges self_dict into app_dict. Replaces None keys with self.name.
Values of dict must be dict.
"""
for key, value in self_dict.items():
key = self.name if key is None else f"{self.name}.{key}"
app_dict[key] = value
app.view_functions.update(self.view_functions)
merge_dict_lists(self.before_request_funcs, app.before_request_funcs)
merge_dict_lists(self.after_request_funcs, app.after_request_funcs)
merge_dict_lists(self.teardown_request_funcs, app.teardown_request_funcs)
merge_dict_lists(self.url_default_functions, app.url_default_functions)
merge_dict_lists(self.url_value_preprocessors, app.url_value_preprocessors)
merge_dict_lists(
self.template_context_processors, app.template_context_processors
)
merge_dict_nested(self.error_handler_spec, app.error_handler_spec)
for deferred in self.deferred_functions:
deferred(state)
@ -258,18 +291,6 @@ class Blueprint(_PackageBoundObject):
self.cli.name = cli_resolved_group
app.cli.add_command(self.cli)
def route(self, rule, **options):
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
:func:`url_for` function is prefixed with the name of the blueprint.
"""
def decorator(f):
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, 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 blueprint. The endpoint for
the :func:`url_for` function is prefixed with the name of the blueprint.
@ -282,23 +303,6 @@ class Blueprint(_PackageBoundObject):
), "Blueprint view function name should not contain dots"
self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
def endpoint(self, endpoint):
"""Like :meth:`Flask.endpoint` but for a blueprint. This does not
prefix the endpoint with the blueprint name, this has to be done
explicitly by the user of this method. If the endpoint is prefixed
with a `.` it will be registered to the current blueprint, otherwise
it's an application independent endpoint.
"""
def decorator(f):
def register_endpoint(state):
state.app.view_functions[endpoint] = f
self.record_once(register_endpoint)
return f
return decorator
def app_template_filter(self, name=None):
"""Register a custom template filter, available application wide. Like
:meth:`Flask.template_filter` but for a blueprint.
@ -391,16 +395,6 @@ class Blueprint(_PackageBoundObject):
self.record_once(register_template)
def before_request(self, f):
"""Like :meth:`Flask.before_request` but for a blueprint. This function
is only executed before each request that is handled by a function of
that blueprint.
"""
self.record_once(
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 blueprint.
@ -417,16 +411,6 @@ class Blueprint(_PackageBoundObject):
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
return f
def after_request(self, f):
"""Like :meth:`Flask.after_request` but for a blueprint. This function
is only executed after each request that is handled by a function of
that blueprint.
"""
self.record_once(
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 blueprint. Such a function
is executed after each request, even if outside of the blueprint.
@ -436,18 +420,6 @@ class Blueprint(_PackageBoundObject):
)
return f
def teardown_request(self, f):
"""Like :meth:`Flask.teardown_request` but for a blueprint. This
function is only executed when tearing down requests handled by a
function of that blueprint. Teardown request functions are executed
when the request context is popped, even when no actual request was
performed.
"""
self.record_once(
lambda s: s.app.teardown_request_funcs.setdefault(self.name, []).append(f)
)
return f
def teardown_app_request(self, f):
"""Like :meth:`Flask.teardown_request` but for a blueprint. Such a
function is executed when tearing down each request, even if outside of
@ -458,17 +430,6 @@ class Blueprint(_PackageBoundObject):
)
return f
def context_processor(self, f):
"""Like :meth:`Flask.context_processor` but for a blueprint. This
function is only executed for requests handled by a blueprint.
"""
self.record_once(
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 blueprint. Such a
function is executed each request, even if outside of the blueprint.
@ -489,26 +450,6 @@ class Blueprint(_PackageBoundObject):
return decorator
def url_value_preprocessor(self, f):
"""Registers a function as URL value preprocessor for this
blueprint. It's called before the view functions are called and
can modify the url values provided.
"""
self.record_once(
lambda s: s.app.url_value_preprocessors.setdefault(self.name, []).append(f)
)
return f
def url_defaults(self, f):
"""Callback function for URL defaults for this blueprint. It's called
with the endpoint and values and should update the values passed
in place.
"""
self.record_once(
lambda s: s.app.url_default_functions.setdefault(self.name, []).append(f)
)
return f
def app_url_value_preprocessor(self, f):
"""Same as :meth:`url_value_preprocessor` but application wide.
"""
@ -524,35 +465,3 @@ class Blueprint(_PackageBoundObject):
lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
)
return f
def errorhandler(self, code_or_exception):
"""Registers an error handler that becomes active for this blueprint
only. Please be aware that routing does not happen local to a
blueprint so an error handler for 404 usually is not handled by
a blueprint unless it is caused inside a view function. Another
special case is the 500 internal server error which is always looked
up from the application.
Otherwise works as the :meth:`~flask.Flask.errorhandler` decorator
of the :class:`~flask.Flask` object.
"""
def decorator(f):
self.record_once(
lambda s: s.app._register_error_handler(self.name, code_or_exception, f)
)
return f
return decorator
def register_error_handler(self, code_or_exception, f):
"""Non-decorator version of the :meth:`errorhandler` error attach
function, akin to the :meth:`~flask.Flask.register_error_handler`
application-wide function of the :class:`~flask.Flask` object but
for error handlers limited to this blueprint.
.. versionadded:: 0.11
"""
self.record_once(
lambda s: s.app._register_error_handler(self.name, code_or_exception, f)
)