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) 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]