From 0746661182d995f1d0950bbf59e34588b76e5654 Mon Sep 17 00:00:00 2001 From: Anthon van der Neut Date: Thu, 27 Jul 2023 21:36:12 +0200 Subject: [PATCH] views: warn if handle_request returns dict With this change a warnings.warn() is invoked when a handle_request returns a dict instead of a response objec. This warning is issued once for each unique file-name, line-number of the "offending" handle_request(s). The warning references the file and line number of the "offending" handle_request method and a link to the Response Objects documentation. The end-user can influence the result of the warning in the usual way by including the following lines: import warnings from lona.warnings import DictResponseDeprecationWarning warnings.simplefilter('always', DictResponseDeprecationWarning) causing the warning to issue every time (other options include 'error' and 'ignore') The warnings.warn mechanism is patched to be able to handle printing callee file-name and line-number (instead of the more usual caller info), in a similar way as the package ruamel.std.warnings. --- lona/view_runtime.py | 11 ++++++++- lona/warnings.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 lona/warnings.py diff --git a/lona/view_runtime.py b/lona/view_runtime.py index 555e7e47..f877693e 100644 --- a/lona/view_runtime.py +++ b/lona/view_runtime.py @@ -42,6 +42,7 @@ from lona.connection import Connection from lona.request import Request from lona.routing import Route +import lona.warnings # avoid import cycles if TYPE_CHECKING: # pragma: no cover @@ -344,8 +345,16 @@ def start(self): self.send_view_start() # run view + handle_request_return_value = self.view.handle_request(self.request) + if isinstance(handle_request_return_value, dict): + lona.warnings.warn( # NOQA: G010 + 'Deprecated use of dict as return value of method handle_request\n (see: https://lona-web.org/1.x/api-reference/views.html#response-objects)', + lona.warnings.DictResponseDeprecationWarning, + callee=self.view.handle_request, + ) + response = parse_view_return_value( - return_value=self.view.handle_request(self.request), + return_value=handle_request_return_value, interactive=self.route and self.route.interactive, ) diff --git a/lona/warnings.py b/lona/warnings.py new file mode 100644 index 00000000..510fc026 --- /dev/null +++ b/lona/warnings.py @@ -0,0 +1,55 @@ +import warnings as orginal_warnings + + +class ExtendedWarn: + warn = orginal_warnings.warn + + def __call__( + self, message, category=None, stacklevel=1, source=None, callee=None, + ): + # Check if message is already a Warning object + if isinstance(message, Warning): + category = message.__class__ + if callee: + assert stacklevel == 1 + if category is not None: + filename = callee.__func__.__code__.co_filename + lineno = callee.__func__.__code__.co_firstlineno + message = ('callee', message, filename, lineno, category) + else: + stacklevel += 1 + self.warn(message, category, stacklevel, source) # NOQA: G010 + + +warn = ExtendedWarn() +orginal_warnings.warn = warn # type: ignore + +_original_formatwarning = orginal_warnings.formatwarning + + +def _formatwarning_with_callee(message, category, filename, lineno, line): + try: + if ( + not isinstance(message, str) + and isinstance(message.args[0], tuple) + and len(message.args[0]) == 5 + and message.args[0][0] == 'callee' + ): + _, message, filename, lineno, category = message.args[0] + except Exception: + # show original e.g. when message is not a string, + # but has not .args attribute + pass + return _original_formatwarning(message, category, filename, lineno, line) + + +orginal_warnings.formatwarning = _formatwarning_with_callee # type: ignore + + +class DictResponseDeprecationWarning(PendingDeprecationWarning): + pass + + +orginal_warnings.simplefilter( + 'once', category=DictResponseDeprecationWarning, +)