From b2e68773eb1e75d2954963061798fee40be8dd64 Mon Sep 17 00:00:00 2001 From: Elizabeth Thompson Date: Mon, 22 Jun 2026 16:20:27 +0000 Subject: [PATCH 1/2] fix(views): suppress repeated deprecated-endpoint log noise The `deprecated` decorator in views/base.py logged a WARNING on every HTTP request to any deprecated endpoint. In production, `explore_json` is called by legacy chart types on every chart render, flooding logs with the same message thousands of times per day and making the signal meaningless. Add a per-endpoint `_warned` flag (closure variable inside `_deprecated`) so the warning fires exactly once per endpoint per worker process. Subsequent requests to the same deprecated endpoint are handled silently. The warning message and format are unchanged; only the repetition is eliminated. A benign first-call race on multi-threaded workers may emit two or three log lines per worker on startup, which is acceptable. Co-Authored-By: Claude Sonnet 4.6 --- superset/views/base.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/superset/views/base.py b/superset/views/base.py index bb8cdf6ac844..4b5ce0b2cbd2 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -171,20 +171,25 @@ def deprecated( """ def _deprecated(f: Callable[..., FlaskResponse]) -> Callable[..., FlaskResponse]: + _warned = False + def wraps(self: BaseSupersetView, *args: Any, **kwargs: Any) -> FlaskResponse: - message = ( - "%s.%s " - "This API endpoint is deprecated and will be removed in version %s" - ) - logger_args = [ - self.__class__.__name__, - f.__name__, - eol_version, - ] - if new_target: - message += " . Use the following API endpoint instead: %s" - logger_args.append(new_target) - logger.warning(message, *logger_args) + nonlocal _warned + if not _warned: + _warned = True + message = ( + "%s.%s " + "This API endpoint is deprecated and will be removed in version %s" + ) + logger_args = [ + self.__class__.__name__, + f.__name__, + eol_version, + ] + if new_target: + message += " . Use the following API endpoint instead: %s" + logger_args.append(new_target) + logger.warning(message, *logger_args) return f(self, *args, **kwargs) return functools.update_wrapper(wraps, f) From 5e0659151d5ff843bf03771dad3782634a7fdc65 Mon Sep 17 00:00:00 2001 From: Elizabeth Thompson Date: Mon, 22 Jun 2026 16:24:06 +0000 Subject: [PATCH 2/2] test(views): add test for deprecated decorator once-per-endpoint behavior Self-review (019ef022-d140-77b3-bd7a-6e32a74ea00b) required a test to guard the _warned closure flag introduced in the prior commit. Verifies that calling a @deprecated-decorated endpoint twice emits exactly one logger.warning call. Co-Authored-By: Claude Sonnet 4.6 --- tests/unit_tests/views/test_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit_tests/views/test_base.py b/tests/unit_tests/views/test_base.py index 87b45eed90e5..1469fe9f2bcf 100644 --- a/tests/unit_tests/views/test_base.py +++ b/tests/unit_tests/views/test_base.py @@ -209,3 +209,21 @@ def test_csv_response_leaves_bytes_untouched() -> None: payload = "Ürün\n".encode("utf-8-sig") assert CsvResponse(payload).get_data() == payload + + +def test_deprecated_logs_warning_exactly_once() -> None: + from superset.views.base import BaseSupersetView, deprecated + + @deprecated(eol_version="5.0.0", new_target="/api/v1/chart/data") + def endpoint(self: BaseSupersetView) -> None: + return None + + view = MagicMock(spec=BaseSupersetView) + view.__class__.__name__ = "Superset" + + with patch("superset.views.base.logger") as mock_logger: + endpoint(view) + endpoint(view) + + assert mock_logger.warning.call_count == 1 + assert "5.0.0" in mock_logger.warning.call_args[0][0]