diff --git a/setup.py b/setup.py index 7de67ca0cd41..f5b062b64cca 100644 --- a/setup.py +++ b/setup.py @@ -113,6 +113,9 @@ def get_git_sha(): "sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562 "typing-extensions>=3.7.4.3,<4", # needed to support typing.Literal on py37 "wtforms-json", + "pyparsing>=2.4.7, <3.0.0", + "holidays==0.10.3", # PINNED! https://github.com/dr-prodigy/python-holidays/issues/406 + "deprecation>=2.1.0, <2.2.0", ], extras_require={ "athena": ["pyathena>=1.10.8,<1.11"], diff --git a/superset/app.py b/superset/app.py index 02682e533387..48883a92648b 100644 --- a/superset/app.py +++ b/superset/app.py @@ -14,21 +14,13 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from __future__ import annotations -import importlib.machinery -import importlib.util import logging import os -import sys -from types import ModuleType -from typing import Any, Dict, Union from flask import Flask -from werkzeug.utils import import_string from superset.initialization import SupersetAppInitializer -from superset.utils.core import is_test logger = logging.getLogger(__name__) @@ -38,8 +30,8 @@ def create_app() -> Flask: try: # Allow user to override our config completely - config = init_config() - app.config.from_mapping(config) + config_module = os.environ.get("SUPERSET_CONFIG", "superset.config") + app.config.from_object(config_module) app_initializer = app.config.get("APP_INITIALIZER", SupersetAppInitializer)(app) app_initializer.init_app() @@ -52,56 +44,5 @@ def create_app() -> Flask: raise ex -def init_config() -> Dict[Any, Any]: - config = convert_to_dict(load_default_config()) - override_conf = convert_to_dict(load_override_config()) - config.update(override_conf) - return config - - -def convert_to_dict(module: Union[ModuleType, Dict[Any, Any]]) -> Dict[Any, Any]: - raw_dict = module if isinstance(module, dict) else module.__dict__ - return {k: v for k, v in raw_dict.items() if k.isupper() and not k.startswith("_")} - - -def load_default_config() -> ModuleType: - config_module = os.environ.get("SUPERSET_CONFIG", "superset.config") - config: ModuleType = import_string(config_module) - return config - - -def load_override_config() -> Union[Dict[Any, Any], ModuleType]: - CONFIG_PATH_ENV_VAR = "SUPERSET_CONFIG_PATH" # pylint: disable=C0103 - if CONFIG_PATH_ENV_VAR in os.environ: - # Explicitly import config module that is not necessarily in pythonpath; useful - # for case where app is being executed via pex. - cfg_path = os.environ[CONFIG_PATH_ENV_VAR] - try: - CONFIG_MODULE_NAME = "superset_config" # pylint: disable=C0103 - loader = importlib.machinery.SourceFileLoader(CONFIG_MODULE_NAME, cfg_path) - spec = importlib.util.spec_from_loader(CONFIG_MODULE_NAME, loader) - override_conf = importlib.util.module_from_spec(spec) - sys.modules[CONFIG_MODULE_NAME] = override_conf - loader.exec_module(override_conf) - - print(f"Loaded your LOCAL configuration at [{cfg_path}]") - return override_conf - except Exception: - logger.exception( - "Failed to import config for %s=%s", CONFIG_PATH_ENV_VAR, cfg_path - ) - raise - elif importlib.util.find_spec("superset_config") and not is_test(): - try: - import superset_config # pylint: disable=import-error - - print(f"Loaded your LOCAL configuration at [{superset_config.__file__}]") - return superset_config - except Exception: - logger.exception("Found but failed to import local superset_config") - raise - return {} - - class SupersetApp(Flask): pass diff --git a/superset/config.py b/superset/config.py index fbd239302cd3..855ba8783d3e 100644 --- a/superset/config.py +++ b/superset/config.py @@ -20,11 +20,13 @@ in your PYTHONPATH as there is a ``from superset_config import *`` at the end of this file. """ - +import imp +import importlib.util import json import logging import os import re +import sys from collections import OrderedDict from datetime import date from typing import Any, Callable, Dict, List, Optional, Type, TYPE_CHECKING, Union @@ -41,7 +43,7 @@ ) from superset.stats_logger import DummyStatsLogger from superset.typing import CacheConfig -from superset.utils.core import parse_boolean_string +from superset.utils.core import is_test, parse_boolean_string from superset.utils.encrypt import SQLAlchemyUtilsAdapter from superset.utils.log import DBEventLogger from superset.utils.logging_configurator import DefaultLoggingConfigurator @@ -856,6 +858,7 @@ def CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC( # by humans. ROBOT_PERMISSION_ROLES = ["Public", "Gamma", "Alpha", "Admin", "sql_lab"] +CONFIG_PATH_ENV_VAR = "SUPERSET_CONFIG_PATH" # If a callable is specified, it will be called at app startup while passing # a reference to the Flask app. This can be used to alter the Flask app @@ -1227,3 +1230,29 @@ def CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC( # ------------------------------------------------------------------- # Don't add config values below this line since local configs won't be # able to override them. +if CONFIG_PATH_ENV_VAR in os.environ: + # Explicitly import config module that is not necessarily in pythonpath; useful + # for case where app is being executed via pex. + try: + cfg_path = os.environ[CONFIG_PATH_ENV_VAR] + module = sys.modules[__name__] + override_conf = imp.load_source("superset_config", cfg_path) + for key in dir(override_conf): + if key.isupper(): + setattr(module, key, getattr(override_conf, key)) + + print(f"Loaded your LOCAL configuration at [{cfg_path}]") + except Exception: + logger.exception( + "Failed to import config for %s=%s", CONFIG_PATH_ENV_VAR, cfg_path + ) + raise +elif importlib.util.find_spec("superset_config") and not is_test(): + try: + import superset_config # pylint: disable=import-error + from superset_config import * # type: ignore # pylint: disable=import-error,wildcard-import,unused-wildcard-import + + print(f"Loaded your LOCAL configuration at [{superset_config.__file__}]") + except Exception: + logger.exception("Found but failed to import local superset_config") + raise