This is an automated email from the ASF dual-hosted git repository.
vavila 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 6fe69fc81c chore: Support specifying app_root via superset_config.py
(#38284)
6fe69fc81c is described below
commit 6fe69fc81cd07f496d227bd94b5eca118e124a9e
Author: Vitor Avila <[email protected]>
AuthorDate: Sat Feb 28 01:35:08 2026 -0300
chore: Support specifying app_root via superset_config.py (#38284)
---
.../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 13b7edd502..657d5d4dc7 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 cf89171a57..3edf16eb41 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 01fde0967c..65d2ea4c96 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"