This is an automated email from the ASF dual-hosted git repository.

eschutho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 8955593b858 fix: close gaps in pkg_resources/sqlalchemy-redshift 
warning suppression (#41935)
8955593b858 is described below

commit 8955593b858f40e6ebafc30bc4199edee24e9e1b
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Wed Jul 29 15:05:18 2026 -0700

    fix: close gaps in pkg_resources/sqlalchemy-redshift warning suppression 
(#41935)
    
    Co-authored-by: Claude Fable 5 <[email protected]>
---
 superset/db_engine_specs/redshift.py            | 16 +++++++++
 superset/initialization/__init__.py             | 15 +++++++++
 superset/mcp_service/__init__.py                | 12 +++++++
 superset/mcp_service/server.py                  | 15 +++++++++
 tests/unit_tests/initialization_test.py         | 45 +++++++++++++++++++++++++
 tests/unit_tests/mcp_service/test_mcp_server.py | 18 ++++++++++
 6 files changed, 121 insertions(+)

diff --git a/superset/db_engine_specs/redshift.py 
b/superset/db_engine_specs/redshift.py
index de6a2a5b514..c4c266ab6e8 100644
--- a/superset/db_engine_specs/redshift.py
+++ b/superset/db_engine_specs/redshift.py
@@ -46,9 +46,25 @@ from superset.utils import json
 # Setuptools 80.x (pinned in requirements/base.txt) raises this as a plain
 # UserWarning, not DeprecationWarning -- don't add category=DeprecationWarning
 # here, it would silently stop matching.
+#
+# Scoped to the sqlalchemy_redshift module (via stacklevel=2 in setuptools'
+# own warn() call, the warning is attributed to whatever imports
+# pkg_resources, i.e. sqlalchemy_redshift/__init__.py) so this doesn't also
+# swallow the same deprecation warning from unrelated dependencies.
+#
+# The same filter is also registered unconditionally in
+# SupersetAppInitializer.configure_logging() (superset/initialization/
+# __init__.py), before it dispatches to the (deployment-replaceable)
+# LOGGING_CONFIGURATOR. That's now the primary suppression point for the
+# web app and celery workers; this one remains as a fallback for standalone
+# scripts that import this module without going through create_app()
+# (filterwarnings() calls are idempotent, so registering it twice is
+# harmless).
 warnings.filterwarnings(
     "ignore",
     message=r"pkg_resources is deprecated as an API",
+    category=UserWarning,
+    module=r"sqlalchemy_redshift(?:\..*)?",
 )
 
 logger = logging.getLogger()
diff --git a/superset/initialization/__init__.py 
b/superset/initialization/__init__.py
index 9bd94bd54ef..851dc04a775 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -20,6 +20,7 @@ import contextlib
 import logging
 import os
 import sys
+import warnings
 from typing import Any, Callable, TYPE_CHECKING
 
 import wtforms_json
@@ -1392,6 +1393,20 @@ class SupersetAppInitializer:  # pylint: 
disable=too-many-public-methods
             )
 
     def configure_logging(self) -> None:
+        # sqlalchemy-redshift's own __init__ still imports pkg_resources (see
+        # superset/db_engine_specs/redshift.py for the full rationale). This
+        # filter used to live in 
DefaultLoggingConfigurator.configure_logging(),
+        # but LOGGING_CONFIGURATOR is a deployment-replaceable hook -- any
+        # custom configurator skipped it entirely and still hit the warning.
+        # Registering it here, before LOGGING_CONFIGURATOR runs, guarantees
+        # it's installed regardless of which configurator is configured.
+        warnings.filterwarnings(
+            "ignore",
+            message=r"pkg_resources is deprecated as an API",
+            category=UserWarning,
+            module=r"sqlalchemy_redshift(?:\..*)?",
+        )
+
         self.config["LOGGING_CONFIGURATOR"].configure_logging(
             self.config, self.superset_app.debug
         )
diff --git a/superset/mcp_service/__init__.py b/superset/mcp_service/__init__.py
index 75949faa7c9..419ed13fe49 100644
--- a/superset/mcp_service/__init__.py
+++ b/superset/mcp_service/__init__.py
@@ -47,6 +47,18 @@ warnings.filterwarnings(
     message=r"authlib\.jose module is deprecated",
 )
 
+# sqlalchemy-redshift's own __init__ still imports pkg_resources (see the
+# equivalent filter and rationale in superset/db_engine_specs/redshift.py).
+# MCP tools that touch a Redshift-backed database trigger this import via a
+# separate process from the main web/worker app, so it needs its own filter
+# here rather than relying on db_engine_specs/redshift.py having been loaded.
+warnings.filterwarnings(
+    "ignore",
+    message=r"pkg_resources is deprecated as an API",
+    category=UserWarning,
+    module=r"sqlalchemy_redshift(?:\..*)?",
+)
+
 __version__ = "1.0.0"
 
 # Tools are auto-registered when imported by the MCP service
diff --git a/superset/mcp_service/server.py b/superset/mcp_service/server.py
index 37101cc0011..630141d712a 100644
--- a/superset/mcp_service/server.py
+++ b/superset/mcp_service/server.py
@@ -63,6 +63,8 @@ def _suppress_third_party_warnings() -> None:
     - marshmallow ``RemovedInMarshmallow4Warning`` (triggered during
       database engine schema instantiation)
     - google.api_core ``FutureWarning`` (Python version support notices)
+    - sqlalchemy-redshift ``pkg_resources`` UserWarning (see
+      superset/db_engine_specs/redshift.py for details)
     """
     import warnings
 
@@ -83,6 +85,19 @@ def _suppress_third_party_warnings() -> None:
         "ignore",
         message=r"authlib\.jose module is deprecated",
     )
+    # Same treatment for the pkg_resources warning suppressed at package
+    # init time. Confirmed non-redundant: warnings.filters can be reset
+    # between the package import and this call (e.g. pytest's warnings
+    # plugin resets it around every test -- test_suppress_third_party_warnings
+    # below fails without this line, proving the reset scenario is real,
+    # not hypothetical), so re-registering here is load-bearing, not
+    # belt-and-suspenders.
+    warnings.filterwarnings(
+        "ignore",
+        message=r"pkg_resources is deprecated as an API",
+        category=UserWarning,
+        module=r"sqlalchemy_redshift(?:\..*)?",
+    )
 
 
 def _downgrade_to_warning(record: logging.LogRecord) -> None:
diff --git a/tests/unit_tests/initialization_test.py 
b/tests/unit_tests/initialization_test.py
index 860063905a0..e68560906ce 100644
--- a/tests/unit_tests/initialization_test.py
+++ b/tests/unit_tests/initialization_test.py
@@ -226,6 +226,51 @@ class TestSupersetAppInitializer:
                 assert "secretpass" not in output
                 assert "postgresql://user:***@localhost:5432/db" in output
 
+    @patch("superset.initialization.logger")
+    def 
test_configure_logging_installs_pkg_resources_filter_before_configurator(
+        self, mock_logger
+    ) -> None:
+        """The pkg_resources warning filter must be installed before
+        LOGGING_CONFIGURATOR.configure_logging() dispatches, so a deployment's
+        custom configurator (which may skip DefaultLoggingConfigurator's own
+        filter) still benefits from it."""
+        import re
+        import warnings
+
+        def has_pkg_resources_filter() -> bool:
+            return any(
+                f[0] == "ignore"
+                and isinstance(f[1], re.Pattern)
+                and f[1].pattern == r"pkg_resources is deprecated as an API"
+                and f[2] is UserWarning
+                and isinstance(f[3], re.Pattern)
+                and f[3].pattern == r"sqlalchemy_redshift(?:\..*)?"
+                for f in warnings.filters
+            )
+
+        seen_during_dispatch = []
+
+        class RecordingConfigurator:
+            def configure_logging(self, app_config, debug_mode):
+                seen_during_dispatch.append(has_pkg_resources_filter())
+
+        mock_app = MagicMock()
+        mock_app.config = {"LOGGING_CONFIGURATOR": RecordingConfigurator()}
+        mock_app.debug = False
+        app_initializer = SupersetAppInitializer(mock_app)
+
+        with warnings.catch_warnings():
+            # Isolate from filters registered by other tests/import side 
effects.
+            warnings.resetwarnings()
+            assert not has_pkg_resources_filter()
+
+            app_initializer.configure_logging()
+
+            assert seen_during_dispatch == [True], (
+                "pkg_resources filter must already be installed by the time "
+                "LOGGING_CONFIGURATOR.configure_logging() runs"
+            )
+
     def test_check_and_warn_database_connection_invalid_uri(self) -> None:
         """Test that invalid URIs are handled safely without crashing."""
         mock_app = MagicMock()
diff --git a/tests/unit_tests/mcp_service/test_mcp_server.py 
b/tests/unit_tests/mcp_service/test_mcp_server.py
index 9b2de2c74ca..4ab76afecaa 100644
--- a/tests/unit_tests/mcp_service/test_mcp_server.py
+++ b/tests/unit_tests/mcp_service/test_mcp_server.py
@@ -151,6 +151,24 @@ def test_suppress_third_party_warnings():
     ]
     assert len(google_filters) >= 1, "Expected google FutureWarning filter"
 
+    # Verify pkg_resources UserWarning filter is installed, scoped to
+    # sqlalchemy_redshift (sqlalchemy-redshift triggers this via a late
+    # import on Redshift-backed connections; see
+    # superset/db_engine_specs/redshift.py for the full rationale). Scoping
+    # by category+module keeps this from also swallowing the same
+    # deprecation message from unrelated dependencies.
+    pkg_resources_filters = [
+        f
+        for f in warnings.filters
+        if f[0] == "ignore"
+        and f[2] is UserWarning
+        and isinstance(f[1], re.Pattern)
+        and f[1].pattern == r"pkg_resources is deprecated as an API"
+        and isinstance(f[3], re.Pattern)
+        and f[3].pattern == r"sqlalchemy_redshift(?:\..*)?"
+    ]
+    assert len(pkg_resources_filters) >= 1, "Expected pkg_resources warning 
filter"
+
 
 def test_create_event_store_returns_none_when_redis_store_fails():
     """EventStore returns None when Redis store creation fails."""

Reply via email to