Modified bluprints.py and debughelpers.py to adhere more to PEP8 standards

This commit is contained in:
Stacey C 2022-06-17 18:39:18 -07:00
parent 7f2a0f4806
commit 8a72c556d6
2 changed files with 55 additions and 13 deletions

View file

@ -12,6 +12,7 @@ from .scaffold import setupmethod
if t.TYPE_CHECKING: # pragma: no cover if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask from .app import Flask
DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
@ -68,6 +69,7 @@ class BlueprintSetupState:
self.url_defaults = dict(self.blueprint.url_values_defaults) self.url_defaults = dict(self.blueprint.url_values_defaults)
self.url_defaults.update(self.options.get("url_defaults", ())) self.url_defaults.update(self.options.get("url_defaults", ()))
def add_url_rule( def add_url_rule(
self, self,
rule: str, rule: str,
@ -82,12 +84,17 @@ class BlueprintSetupState:
if self.url_prefix is not None: if self.url_prefix is not None:
if rule: if rule:
rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/"))) rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
else: else:
rule = self.url_prefix rule = self.url_prefix
options.setdefault("subdomain", self.subdomain) options.setdefault("subdomain", self.subdomain)
if endpoint is None: if endpoint is None:
endpoint = _endpoint_from_view_func(view_func) # type: ignore endpoint = _endpoint_from_view_func(view_func) # type: ignore
defaults = self.url_defaults defaults = self.url_defaults
if "defaults" in options: if "defaults" in options:
defaults = dict(defaults, **options.pop("defaults")) defaults = dict(defaults, **options.pop("defaults"))
@ -162,6 +169,7 @@ class Blueprint(Scaffold):
#: the app's :class:`~flask.Flask.json_decoder`. #: the app's :class:`~flask.Flask.json_decoder`.
json_decoder = None json_decoder = None
def __init__( def __init__(
self, self,
name: str, name: str,
@ -198,7 +206,9 @@ class Blueprint(Scaffold):
self.cli_group = cli_group self.cli_group = cli_group
self._blueprints: t.List[t.Tuple["Blueprint", dict]] = [] self._blueprints: t.List[t.Tuple["Blueprint", dict]] = []
def _check_setup_finished(self, f_name: str) -> None: def _check_setup_finished(self, f_name: str) -> None:
# TODO - This method is missing a docstring - please advise
if self._got_registered_once: if self._got_registered_once:
import warnings import warnings
@ -215,6 +225,7 @@ class Blueprint(Scaffold):
stacklevel=3, stacklevel=3,
) )
@setupmethod @setupmethod
def record(self, func: t.Callable) -> None: def record(self, func: t.Callable) -> None:
"""Registers a function that is called when the blueprint is """Registers a function that is called when the blueprint is
@ -224,6 +235,7 @@ class Blueprint(Scaffold):
""" """
self.deferred_functions.append(func) self.deferred_functions.append(func)
@setupmethod @setupmethod
def record_once(self, func: t.Callable) -> None: def record_once(self, func: t.Callable) -> None:
"""Works like :meth:`record` but wraps the function in another """Works like :meth:`record` but wraps the function in another
@ -231,13 +243,13 @@ class Blueprint(Scaffold):
blueprint is registered a second time on the application, the blueprint is registered a second time on the application, the
function passed is not called. function passed is not called.
""" """
def wrapper(state: BlueprintSetupState) -> None: def wrapper(state: BlueprintSetupState) -> None:
if state.first_registration: if state.first_registration:
func(state) func(state)
return self.record(update_wrapper(wrapper, func)) return self.record(update_wrapper(wrapper, func))
def make_setup_state( def make_setup_state(
self, app: "Flask", options: dict, first_registration: bool = False self, app: "Flask", options: dict, first_registration: bool = False
) -> BlueprintSetupState: ) -> BlueprintSetupState:
@ -247,6 +259,7 @@ class Blueprint(Scaffold):
""" """
return BlueprintSetupState(self, app, options, first_registration) return BlueprintSetupState(self, app, options, first_registration)
@setupmethod @setupmethod
def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None:
"""Register a :class:`~flask.Blueprint` on this blueprint. Keyword """Register a :class:`~flask.Blueprint` on this blueprint. Keyword
@ -265,6 +278,7 @@ class Blueprint(Scaffold):
raise ValueError("Cannot register a blueprint on itself") raise ValueError("Cannot register a blueprint on itself")
self._blueprints.append((blueprint, options)) self._blueprints.append((blueprint, options))
def register(self, app: "Flask", options: dict) -> None: def register(self, app: "Flask", options: dict) -> None:
"""Called by :meth:`Flask.register_blueprint` to register all """Called by :meth:`Flask.register_blueprint` to register all
views and callbacks registered on the blueprint with the views and callbacks registered on the blueprint with the
@ -322,11 +336,13 @@ class Blueprint(Scaffold):
# Merge blueprint data into parent. # Merge blueprint data into parent.
if first_bp_registration or first_name_registration: if first_bp_registration or first_name_registration:
def extend(bp_dict, parent_dict): def extend(bp_dict, parent_dict):
for key, values in bp_dict.items(): for key, values in bp_dict.items():
key = name if key is None else f"{name}.{key}" key = name if key is None else f"{name}.{key}"
parent_dict[key].extend(values) parent_dict[key].extend(values)
for key, value in self.error_handler_spec.items(): for key, value in self.error_handler_spec.items():
key = name if key is None else f"{name}.{key}" key = name if key is None else f"{name}.{key}"
value = defaultdict( value = defaultdict(
@ -361,9 +377,11 @@ class Blueprint(Scaffold):
if self.cli.commands: if self.cli.commands:
if cli_resolved_group is None: if cli_resolved_group is None:
app.cli.commands.update(self.cli.commands) app.cli.commands.update(self.cli.commands)
elif cli_resolved_group is _sentinel: elif cli_resolved_group is _sentinel:
self.cli.name = name self.cli.name = name
app.cli.add_command(self.cli) app.cli.add_command(self.cli)
else: else:
self.cli.name = cli_resolved_group self.cli.name = cli_resolved_group
app.cli.add_command(self.cli) app.cli.add_command(self.cli)
@ -379,14 +397,17 @@ class Blueprint(Scaffold):
bp_options["url_prefix"] = ( bp_options["url_prefix"] = (
state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
) )
elif bp_url_prefix is not None: elif bp_url_prefix is not None:
bp_options["url_prefix"] = bp_url_prefix bp_options["url_prefix"] = bp_url_prefix
elif state.url_prefix is not None: elif state.url_prefix is not None:
bp_options["url_prefix"] = state.url_prefix bp_options["url_prefix"] = state.url_prefix
bp_options["name_prefix"] = name bp_options["name_prefix"] = name
blueprint.register(app, bp_options) blueprint.register(app, bp_options)
@setupmethod @setupmethod
def add_url_rule( def add_url_rule(
self, self,
@ -415,6 +436,7 @@ class Blueprint(Scaffold):
) )
) )
@setupmethod @setupmethod
def app_template_filter( def app_template_filter(
self, name: t.Optional[str] = None self, name: t.Optional[str] = None
@ -425,13 +447,13 @@ class Blueprint(Scaffold):
:param name: the optional name of the filter, otherwise the :param name: the optional name of the filter, otherwise the
function name will be used. function name will be used.
""" """
def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable: def decorator(f: ft.TemplateFilterCallable) -> ft.TemplateFilterCallable:
self.add_app_template_filter(f, name=name) self.add_app_template_filter(f, name=name)
return f return f
return decorator return decorator
@setupmethod @setupmethod
def add_app_template_filter( def add_app_template_filter(
self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None
@ -443,12 +465,12 @@ class Blueprint(Scaffold):
:param name: the optional name of the filter, otherwise the :param name: the optional name of the filter, otherwise the
function name will be used. function name will be used.
""" """
def register_template(state: BlueprintSetupState) -> None: def register_template(state: BlueprintSetupState) -> None:
state.app.jinja_env.filters[name or f.__name__] = f state.app.jinja_env.filters[name or f.__name__] = f
self.record_once(register_template) self.record_once(register_template)
@setupmethod @setupmethod
def app_template_test( def app_template_test(
self, name: t.Optional[str] = None self, name: t.Optional[str] = None
@ -461,7 +483,6 @@ class Blueprint(Scaffold):
:param name: the optional name of the test, otherwise the :param name: the optional name of the test, otherwise the
function name will be used. function name will be used.
""" """
def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable: def decorator(f: ft.TemplateTestCallable) -> ft.TemplateTestCallable:
self.add_app_template_test(f, name=name) self.add_app_template_test(f, name=name)
return f return f
@ -481,12 +502,12 @@ class Blueprint(Scaffold):
:param name: the optional name of the test, otherwise the :param name: the optional name of the test, otherwise the
function name will be used. function name will be used.
""" """
def register_template(state: BlueprintSetupState) -> None: def register_template(state: BlueprintSetupState) -> None:
state.app.jinja_env.tests[name or f.__name__] = f state.app.jinja_env.tests[name or f.__name__] = f
self.record_once(register_template) self.record_once(register_template)
@setupmethod @setupmethod
def app_template_global( def app_template_global(
self, name: t.Optional[str] = None self, name: t.Optional[str] = None
@ -499,13 +520,13 @@ class Blueprint(Scaffold):
:param name: the optional name of the global, otherwise the :param name: the optional name of the global, otherwise the
function name will be used. function name will be used.
""" """
def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable: def decorator(f: ft.TemplateGlobalCallable) -> ft.TemplateGlobalCallable:
self.add_app_template_global(f, name=name) self.add_app_template_global(f, name=name)
return f return f
return decorator return decorator
@setupmethod @setupmethod
def add_app_template_global( def add_app_template_global(
self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None
@ -519,12 +540,12 @@ class Blueprint(Scaffold):
:param name: the optional name of the global, otherwise the :param name: the optional name of the global, otherwise the
function name will be used. function name will be used.
""" """
def register_template(state: BlueprintSetupState) -> None: def register_template(state: BlueprintSetupState) -> None:
state.app.jinja_env.globals[name or f.__name__] = f state.app.jinja_env.globals[name or f.__name__] = f
self.record_once(register_template) self.record_once(register_template)
@setupmethod @setupmethod
def before_app_request( def before_app_request(
self, f: ft.BeforeRequestCallable self, f: ft.BeforeRequestCallable
@ -537,6 +558,7 @@ class Blueprint(Scaffold):
) )
return f return f
@setupmethod @setupmethod
def before_app_first_request( def before_app_first_request(
self, f: ft.BeforeFirstRequestCallable self, f: ft.BeforeFirstRequestCallable
@ -557,9 +579,11 @@ class Blueprint(Scaffold):
DeprecationWarning, DeprecationWarning,
stacklevel=2, stacklevel=2,
) )
self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
return f return f
def after_app_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable: def after_app_request(self, f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
"""Like :meth:`Flask.after_request` but for a blueprint. Such a function """Like :meth:`Flask.after_request` but for a blueprint. Such a function
is executed after each request, even if outside of the blueprint. is executed after each request, even if outside of the blueprint.
@ -569,6 +593,7 @@ class Blueprint(Scaffold):
) )
return f return f
@setupmethod @setupmethod
def teardown_app_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable: def teardown_app_request(self, f: ft.TeardownCallable) -> ft.TeardownCallable:
"""Like :meth:`Flask.teardown_request` but for a blueprint. Such a """Like :meth:`Flask.teardown_request` but for a blueprint. Such a
@ -580,6 +605,7 @@ class Blueprint(Scaffold):
) )
return f return f
@setupmethod @setupmethod
def app_context_processor( def app_context_processor(
self, f: ft.TemplateContextProcessorCallable self, f: ft.TemplateContextProcessorCallable
@ -592,6 +618,7 @@ class Blueprint(Scaffold):
) )
return f return f
@setupmethod @setupmethod
def app_errorhandler( def app_errorhandler(
self, code: t.Union[t.Type[Exception], int] self, code: t.Union[t.Type[Exception], int]
@ -599,13 +626,13 @@ class Blueprint(Scaffold):
"""Like :meth:`Flask.errorhandler` but for a blueprint. This """Like :meth:`Flask.errorhandler` but for a blueprint. This
handler is used for all requests, even if outside of the blueprint. handler is used for all requests, even if outside of the blueprint.
""" """
def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator: def decorator(f: ft.ErrorHandlerDecorator) -> ft.ErrorHandlerDecorator:
self.record_once(lambda s: s.app.errorhandler(code)(f)) self.record_once(lambda s: s.app.errorhandler(code)(f))
return f return f
return decorator return decorator
@setupmethod @setupmethod
def app_url_value_preprocessor( def app_url_value_preprocessor(
self, f: ft.URLValuePreprocessorCallable self, f: ft.URLValuePreprocessorCallable
@ -616,6 +643,7 @@ class Blueprint(Scaffold):
) )
return f return f
@setupmethod @setupmethod
def app_url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable: def app_url_defaults(self, f: ft.URLDefaultCallable) -> ft.URLDefaultCallable:
"""Same as :meth:`url_defaults` but application wide.""" """Same as :meth:`url_defaults` but application wide."""

View file

@ -9,6 +9,7 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError):
"""Raised in places where we want some better error reporting for """Raised in places where we want some better error reporting for
unexpected unicode or binary data. unexpected unicode or binary data.
""" """
pass
class DebugFilesKeyError(KeyError, AssertionError): class DebugFilesKeyError(KeyError, AssertionError):
@ -26,6 +27,7 @@ class DebugFilesKeyError(KeyError, AssertionError):
" were transmitted. To fix this error you should provide" " were transmitted. To fix this error you should provide"
' enctype="multipart/form-data" in your form.' ' enctype="multipart/form-data" in your form.'
] ]
if form_matches: if form_matches:
names = ", ".join(repr(x) for x in form_matches) names = ", ".join(repr(x) for x in form_matches)
buf.append( buf.append(
@ -71,16 +73,16 @@ class FormDataRoutingRedirect(AssertionError):
def attach_enctype_error_multidict(request): def attach_enctype_error_multidict(request):
"""Patch ``request.files.__getitem__`` to raise a descriptive error """Patch ``request.files.__getitem__`` to raise a descriptive error
about ``enctype=multipart/form-data``. about ``enctype=multipart/form-data``.
:param request: The request to patch. :param request: The request to patch.
:meta private: :meta private:
""" """
oldcls = request.files.__class__ oldcls = request.files.__class__
class newcls(oldcls): class NewCls(oldcls):
def __getitem__(self, key): def __getitem__(self, key):
try: try:
return super().__getitem__(key) return super().__getitem__(key)
except KeyError as e: except KeyError as e:
if key not in request.form: if key not in request.form:
raise raise
@ -89,23 +91,28 @@ def attach_enctype_error_multidict(request):
e.__traceback__ e.__traceback__
) from None ) from None
newcls.__name__ = oldcls.__name__ NewCls.__name__ = oldcls.__name__
newcls.__module__ = oldcls.__module__ NewCls.__module__ = oldcls.__module__
request.files.__class__ = newcls request.files.__class__ = NewCls
def _dump_loader_info(loader) -> t.Generator: def _dump_loader_info(loader) -> t.Generator:
# TODO - This method is missing a docstring - please advise
yield f"class: {type(loader).__module__}.{type(loader).__name__}" yield f"class: {type(loader).__module__}.{type(loader).__name__}"
for key, value in sorted(loader.__dict__.items()): for key, value in sorted(loader.__dict__.items()):
if key.startswith("_"): if key.startswith("_"):
continue continue
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
if not all(isinstance(x, str) for x in value): if not all(isinstance(x, str) for x in value):
continue continue
yield f"{key}:" yield f"{key}:"
for item in value: for item in value:
yield f" - {item}" yield f" - {item}"
continue continue
elif not isinstance(value, (str, int, float, bool)): elif not isinstance(value, (str, int, float, bool)):
continue continue
yield f"{key}: {value!r}" yield f"{key}: {value!r}"
@ -117,14 +124,17 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
total_found = 0 total_found = 0
blueprint = None blueprint = None
reqctx = _request_ctx_stack.top reqctx = _request_ctx_stack.top
if reqctx is not None and reqctx.request.blueprint is not None: if reqctx is not None and reqctx.request.blueprint is not None:
blueprint = reqctx.request.blueprint blueprint = reqctx.request.blueprint
for idx, (loader, srcobj, triple) in enumerate(attempts): for idx, (loader, srcobj, triple) in enumerate(attempts):
if isinstance(srcobj, Flask): if isinstance(srcobj, Flask):
src_info = f"application {srcobj.import_name!r}" src_info = f"application {srcobj.import_name!r}"
elif isinstance(srcobj, Blueprint): elif isinstance(srcobj, Blueprint):
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})" src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
else: else:
src_info = repr(srcobj) src_info = repr(srcobj)
@ -135,15 +145,19 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
if triple is None: if triple is None:
detail = "no match" detail = "no match"
else: else:
detail = f"found ({triple[1] or '<string>'!r})" detail = f"found ({triple[1] or '<string>'!r})"
total_found += 1 total_found += 1
info.append(f" -> {detail}") info.append(f" -> {detail}")
seems_fishy = False seems_fishy = False
if total_found == 0: if total_found == 0:
info.append("Error: the template could not be found.") info.append("Error: the template could not be found.")
seems_fishy = True seems_fishy = True
elif total_found > 1: elif total_found > 1:
info.append("Warning: multiple loaders returned a match for the template.") info.append("Warning: multiple loaders returned a match for the template.")
seems_fishy = True seems_fishy = True