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

vavila pushed a commit to branch chore/document-app-root-config
in repository https://gitbox.apache.org/repos/asf/superset.git

commit f5e1e991bd3f1933378b727884e4d18123dbb831
Author: Vitor Avila <[email protected]>
AuthorDate: Fri Feb 27 00:07:26 2026 -0300

    chore: Support specifying app_root via superset_config.py
---
 .../configuration/configuring-superset.mdx         |  7 ++-
 superset/app.py                                    |  5 +-
 tests/unit_tests/initialization_test.py            | 72 +++++++++++++++++++++-
 3 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/docs/admin_docs/configuration/configuring-superset.mdx 
b/docs/admin_docs/configuration/configuring-superset.mdx
index 13b7edd5026..657d5d4dc75 100644
--- a/docs/admin_docs/configuration/configuring-superset.mdx
+++ b/docs/admin_docs/configuration/configuring-superset.mdx
@@ -220,11 +220,12 @@ RequestHeader set X-Forwarded-Proto "https"
 *Please be advised that this feature is in BETA.*
 
 Superset supports running the application under a non-root path. The root path
-prefix can be specified in one of two ways:
+prefix can be specified in one of three ways:
 
-- Setting the `SUPERSET_APP_ROOT` environment variable to the desired prefix.
 - Customizing the [Flask 
entrypoint](https://github.com/apache/superset/blob/master/superset/app.py#L29)
-  by passing the `superset_app_root` variable.
+  by passing the `superset_app_root` variable; or
+- Setting the `SUPERSET_APP_ROOT` environment variable to the desired prefix; 
or
+- Setting the `APPLICATION_ROOT` config in your `superset_config.py` file.
 
 Note, the prefix should start with a `/`.
 
diff --git a/superset/app.py b/superset/app.py
index cf89171a570..3edf16eb41c 100644
--- a/superset/app.py
+++ b/superset/app.py
@@ -60,7 +60,10 @@ def create_app(
         # Allow application to sit on a non-root path
         # *Please be advised that this feature is in BETA.*
         app_root = cast(
-            str, superset_app_root or os.environ.get("SUPERSET_APP_ROOT", "/")
+            str,
+            superset_app_root
+            or os.environ.get("SUPERSET_APP_ROOT")
+            or app.config["APPLICATION_ROOT"],
         )
         if app_root != "/":
             app.wsgi_app = AppRootMiddleware(app.wsgi_app, app_root)
diff --git a/tests/unit_tests/initialization_test.py 
b/tests/unit_tests/initialization_test.py
index 01fde0967c9..65d2ea4c96d 100644
--- a/tests/unit_tests/initialization_test.py
+++ b/tests/unit_tests/initialization_test.py
@@ -15,11 +15,12 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import os
 from unittest.mock import MagicMock, patch
 
 from sqlalchemy.exc import OperationalError
 
-from superset.app import SupersetApp
+from superset.app import AppRootMiddleware, create_app, SupersetApp
 from superset.initialization import SupersetAppInitializer
 
 
@@ -187,3 +188,72 @@ class TestSupersetAppInitializer:
             app_initializer._db_uri_cache
             == "postgresql://realuser:realpass@realhost:5432/realdb"
         )
+
+
+class TestCreateAppRoot:
+    """Test app root resolution precedence in create_app."""
+
+    @patch("superset.initialization.SupersetAppInitializer.init_app")
+    def test_default_app_root_no_middleware(self, mock_init_app):
+        """No param, no config, no env var: app_root is '/', no middleware."""
+        env = os.environ.copy()
+        env.pop("SUPERSET_APP_ROOT", None)
+        env.pop("SUPERSET_CONFIG", None)
+        with patch.dict(os.environ, env, clear=True):
+            app = create_app()
+
+        assert not isinstance(app.wsgi_app, AppRootMiddleware)
+
+    @patch("superset.initialization.SupersetAppInitializer.init_app")
+    def test_application_root_config_activates_middleware(self, mock_init_app):
+        """APPLICATION_ROOT in config activates AppRootMiddleware."""
+        env = os.environ.copy()
+        env.pop("SUPERSET_APP_ROOT", None)
+        env.pop("SUPERSET_CONFIG", None)
+        with (
+            patch.dict(os.environ, env, clear=True),
+            patch("superset.config.APPLICATION_ROOT", "/from-config", 
create=True),
+        ):
+            app = create_app()
+
+        assert isinstance(app.wsgi_app, AppRootMiddleware)
+        assert app.wsgi_app.app_root == "/from-config"
+
+    @patch("superset.initialization.SupersetAppInitializer.init_app")
+    def test_env_var_activates_middleware(self, mock_init_app):
+        """SUPERSET_APP_ROOT env var activates AppRootMiddleware."""
+        env = os.environ.copy()
+        env.pop("SUPERSET_CONFIG", None)
+        env["SUPERSET_APP_ROOT"] = "/from-env"
+        with patch.dict(os.environ, env, clear=True):
+            app = create_app()
+
+        assert isinstance(app.wsgi_app, AppRootMiddleware)
+        assert app.wsgi_app.app_root == "/from-env"
+
+    @patch("superset.initialization.SupersetAppInitializer.init_app")
+    def test_env_var_takes_precedence_over_config(self, mock_init_app):
+        """SUPERSET_APP_ROOT env var wins over APPLICATION_ROOT config."""
+        env = os.environ.copy()
+        env.pop("SUPERSET_CONFIG", None)
+        env["SUPERSET_APP_ROOT"] = "/from-env"
+        with (
+            patch.dict(os.environ, env, clear=True),
+            patch("superset.config.APPLICATION_ROOT", "/from-config", 
create=True),
+        ):
+            app = create_app()
+
+        assert isinstance(app.wsgi_app, AppRootMiddleware)
+        assert app.wsgi_app.app_root == "/from-env"
+
+    @patch("superset.initialization.SupersetAppInitializer.init_app")
+    def test_param_takes_precedence_over_env_var(self, mock_init_app):
+        """superset_app_root param wins over SUPERSET_APP_ROOT env var."""
+        env = os.environ.copy()
+        env.pop("SUPERSET_CONFIG", None)
+        env["SUPERSET_APP_ROOT"] = "/from-env"
+        with patch.dict(os.environ, env, clear=True):
+            app = create_app(superset_app_root="/from-param")
+
+        assert isinstance(app.wsgi_app, AppRootMiddleware)
+        assert app.wsgi_app.app_root == "/from-param"

Reply via email to