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 010813b57bfe7ac6ca93db2fa37913a0f8341d3c 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..1a8361f290b 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 `APPLICATION_ROOT` config in your `superset_config.py` file; or +- Setting the `SUPERSET_APP_ROOT` environment variable to the desired prefix. 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"
