Repository: apache/superset
File: superset/utils/cache_manager.py (Python)

Code before:
        # rename data keys for existing cache based on new TemporaryExploreState model
        if isinstance(cache, dict):
            cache = {
                ("datasource_id" if key == "dataset_id" else key): value
                for (key, value) in cache.items()
            }
            # add default datasource_type if it doesn't exist
            # temporarily defaulting to table until sqlatables are deprecated
            if "datasource_type" not in cache:
                cache["datasource_type"] = DatasourceType.TABLE

        return cache


class CacheManager:
    def __init__(self) -> None:
        super().__init__()

        self._cache = Cache()
        self._data_cache = Cache()
        self._thumbnail_cache = Cache()
        self._filter_state_cache = Cache()
        self._explore_form_data_cache = ExploreFormDataCache()

    @staticmethod
    def _init_cache(
        app: Flask, cache: Cache, cache_config_key: str, required: bool = False
    ) -> None:
        cache_config = app.config[cache_config_key]
        cache_type = cache_config.get("CACHE_TYPE")
        if (required and cache_type is None) or cache_type == "SupersetMetastoreCache":
            if cache_type is None and not app.debug:
                logger.warning(
                    "Falling back to the built-in cache, that stores data in the "
                    "metadata database, for the following cache: `%s`. "
                    "It is recommended to use `RedisCache`, `MemcachedCache` or "
                    "another dedicated caching backend for production deployments",
                    cache_config_key,
                )
            cache_type = CACHE_IMPORT_PATH
            cache_key_prefix = cache_config.get("CACHE_KEY_PREFIX", cache_config_key)
            cache_config.update(
                {"CACHE_TYPE": cache_type, "CACHE_KEY_PREFIX": cache_key_prefix}
            )

        if cache_type is not None and "CACHE_DEFAULT_TIMEOUT" not in cache_config:
            default_timeout = app.config.get("CACHE_DEFAULT_TIMEOUT")
            cache_config["CACHE_DEFAULT_TIMEOUT"] = default_timeout

        cache.init_app(app, cache_config)


Review comment (performance):
If I got this correctly, users need to have a `HASH_ALGORITHM` defined in `superset_config.py` or else this will raise, right? 

I'm thinking we either need to add a mention to `UPDATING.md` that users need to define a value, or even better you could add `HASH_ALGORITHM` to the `config.py` with the default value set to `md5` (to make sure existing cache keys would still match). What do you think?

Code after:
class ConfigurableHashMethod:
    """
    A callable that defers hash algorithm selection to runtime.

    Flask-caching's memoize decorator evaluates hash_method at decoration time
    (module import), but we need to read HASH_ALGORITHM config at function call
    time when the app context is available.

    This class acts like a hashlib function but looks up the configured
    algorithm when called.
    """

    def __call__(self, data: bytes = b"") -> Any:
        """
        Create a hash object using the configured algorithm.

        Args:
            data: Optional initial data to hash

        Returns:
            A hashlib hash object (e.g., sha256 or md5)

        Raises:
            ValueError: If HASH_ALGORITHM is set to an unsupported value
        """
        algorithm = current_app.config["HASH_ALGORITHM"]
        hash_func = _HASH_METHODS.get(algorithm)
        if hash_func is None:
            raise ValueError(f"Unsupported hash algorithm: {algorithm}")
        return hash_func(data)


# Singleton instance to use as default hash_method
configurable_hash_method = ConfigurableHashMethod()


class SupersetCache(Cache):
    """
    Cache subclass that uses the configured HASH_ALGORITHM instead of MD5.

    Flask-caching uses MD5 by default for cache key generation, which fails
    in FIPS mode where MD5 is disabled. This class overrides the default
    hash method to use the algorithm specified by HASH_ALGORITHM config.

    Note: Switching hash algorithms will invalidate existing cache keys,
    causing a one-time cache miss on upgrade.
    """

    def memoize(
        self,
        timeout: int | None = None,