You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/10/27 05:56:22 UTC
(superset) branch refactor/cache-manager-hash-dedup created (now 66a42ab558c)
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a change to branch refactor/cache-manager-hash-dedup
in repository https://gitbox.apache.org/repos/asf/superset.git
at 66a42ab558c refactor(utils): dedupe hash-algorithm dispatch between hashing.py and cache_manager.py
This branch includes the following new commits:
new 66a42ab558c refactor(utils): dedupe hash-algorithm dispatch between hashing.py and cache_manager.py
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: refactor(utils): dedupe hash-algorithm dispatch between hashing.py and cache_manager.py
Posted by ru...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch refactor/cache-manager-hash-dedup
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 66a42ab558cf607061616f184a4a2e37a09c04c2
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Mon Oct 26 22:53:04 2026 -0700
refactor(utils): dedupe hash-algorithm dispatch between hashing.py and cache_manager.py
superset/utils/cache_manager.py carried its own copy of the algorithm-name
to hashlib-constructor mapping, flagged by its own comment: "Hash function
lookup table matching superset.utils.hashing". That raw-constructor mapping
({"sha256": hashlib.sha256, "md5": hashlib.md5}) is now defined once, in
hashing.py as _HASH_CONSTRUCTORS, and cache_manager.py imports it instead of
redefining it.
hashing.py's own _HASH_FUNCTIONS (algorithm name -> hex-digest string, used
by hash_from_str/hash_from_dict) is now derived from _HASH_CONSTRUCTORS
rather than re-hardcoding the hashlib calls, avoiding a second duplicate.
The two dicts are NOT interchangeable: _HASH_CONSTRUCTORS returns a raw
hashlib constructor (what Flask-Caching's hash_method contract requires -
an object with .update()/.hexdigest()), while _HASH_FUNCTIONS returns a
final hex string. Only the shared constructor mapping was deduplicated;
each file's distinct return-shape contract is preserved byte-for-byte.
Also: ConfigurableHashMethod.__call__ now calls hashing.py's
get_hash_algorithm() instead of re-reading current_app.config directly,
since that's exactly what get_hash_algorithm() already does. No behavior
change - same ValueError on an unsupported algorithm, same hash-object
return contract.
Co-Authored-By: Evan Rusackas <ev...@rusackas.com>
Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
superset/utils/cache_manager.py | 14 ++++----------
superset/utils/hashing.py | 15 ++++++++++++---
tests/unit_tests/utils/test_cache_manager.py | 26 ++++++++++----------------
3 files changed, 26 insertions(+), 29 deletions(-)
diff --git a/superset/utils/cache_manager.py b/superset/utils/cache_manager.py
index 3fef84fc78d..e95958bd4e3 100644
--- a/superset/utils/cache_manager.py
+++ b/superset/utils/cache_manager.py
@@ -16,15 +16,15 @@
# under the License.
from __future__ import annotations
-import hashlib
import logging
from typing import Any, Callable, Optional, TYPE_CHECKING, Union
-from flask import current_app, Flask
+from flask import Flask
from flask_caching import Cache
from markupsafe import Markup
from superset.utils.core import DatasourceType
+from superset.utils.hashing import _HASH_CONSTRUCTORS, get_hash_algorithm
if TYPE_CHECKING:
from superset.coordination.cache_backend import (
@@ -39,12 +39,6 @@ logger = logging.getLogger(__name__)
CACHE_IMPORT_PATH = "superset.extensions.metastore_cache.SupersetMetastoreCache"
-# Hash function lookup table matching superset.utils.hashing
-_HASH_METHODS: dict[str, Callable[..., Any]] = {
- "sha256": hashlib.sha256,
- "md5": hashlib.md5,
-}
-
class ConfigurableHashMethod:
"""
@@ -71,8 +65,8 @@ class ConfigurableHashMethod:
Raises:
ValueError: If HASH_ALGORITHM is set to an unsupported value
"""
- algorithm = current_app.config["HASH_ALGORITHM"]
- hash_func = _HASH_METHODS.get(algorithm)
+ algorithm = get_hash_algorithm()
+ hash_func = _HASH_CONSTRUCTORS.get(algorithm)
if hash_func is None:
raise ValueError(f"Unsupported hash algorithm: {algorithm}")
return hash_func(data)
diff --git a/superset/utils/hashing.py b/superset/utils/hashing.py
index e1ff1a08927..44c44c56c67 100644
--- a/superset/utils/hashing.py
+++ b/superset/utils/hashing.py
@@ -28,10 +28,19 @@ logger = logging.getLogger(__name__)
HashAlgorithm = Literal["md5", "sha256"]
+# Hash algorithm lookup table mapping algorithm name to the raw hashlib
+# constructor. Shared with superset.utils.cache_manager, which needs the
+# constructor itself (not a finalized digest) to satisfy Flask-Caching's
+# hash_method contract.
+_HASH_CONSTRUCTORS: dict[HashAlgorithm, Callable[[bytes], Any]] = {
+ "sha256": hashlib.sha256,
+ "md5": hashlib.md5, # noqa: S324
+}
+
# Hash function lookup table for efficient dispatch
-_HASH_FUNCTIONS: dict[str, Callable[[bytes], str]] = {
- "sha256": lambda data: hashlib.sha256(data).hexdigest(),
- "md5": lambda data: hashlib.md5(data).hexdigest(), # noqa: S324
+_HASH_FUNCTIONS: dict[HashAlgorithm, Callable[[bytes], str]] = {
+ name: (lambda ctor: lambda data: ctor(data).hexdigest())(ctor)
+ for name, ctor in _HASH_CONSTRUCTORS.items()
}
diff --git a/tests/unit_tests/utils/test_cache_manager.py b/tests/unit_tests/utils/test_cache_manager.py
index 188039313b6..747a57e35cf 100644
--- a/tests/unit_tests/utils/test_cache_manager.py
+++ b/tests/unit_tests/utils/test_cache_manager.py
@@ -61,10 +61,9 @@ def test_semantic_coordination_exposes_only_typed_capability() -> None:
def test_configurable_hash_method_uses_sha256():
"""Test ConfigurableHashMethod uses sha256 when configured."""
- mock_app = MagicMock()
- mock_app.config = {"HASH_ALGORITHM": "sha256"}
-
- with patch("superset.utils.cache_manager.current_app", mock_app):
+ with patch(
+ "superset.utils.cache_manager.get_hash_algorithm", return_value="sha256"
+ ):
hash_obj = configurable_hash_method(b"test")
# Verify it returns a sha256 hash object
assert hash_obj.hexdigest() == hashlib.sha256(b"test").hexdigest()
@@ -72,10 +71,7 @@ def test_configurable_hash_method_uses_sha256():
def test_configurable_hash_method_uses_md5():
"""Test ConfigurableHashMethod uses md5 when configured."""
- mock_app = MagicMock()
- mock_app.config = {"HASH_ALGORITHM": "md5"}
-
- with patch("superset.utils.cache_manager.current_app", mock_app):
+ with patch("superset.utils.cache_manager.get_hash_algorithm", return_value="md5"):
hash_obj = configurable_hash_method(b"test")
# Verify it returns a md5 hash object
assert hash_obj.hexdigest() == hashlib.md5(b"test").hexdigest() # noqa: S324
@@ -83,10 +79,9 @@ def test_configurable_hash_method_uses_md5():
def test_configurable_hash_method_empty_data():
"""Test ConfigurableHashMethod with empty data."""
- mock_app = MagicMock()
- mock_app.config = {"HASH_ALGORITHM": "sha256"}
-
- with patch("superset.utils.cache_manager.current_app", mock_app):
+ with patch(
+ "superset.utils.cache_manager.get_hash_algorithm", return_value="sha256"
+ ):
hash_obj = configurable_hash_method()
assert hash_obj.hexdigest() == hashlib.sha256(b"").hexdigest()
@@ -194,9 +189,8 @@ def test_superset_cache_memoize_make_cache_key_allows_explicit_hash():
)
def test_configurable_hash_method_parametrized(algorithm, expected_digest):
"""Parametrized test for ConfigurableHashMethod with different algorithms."""
- mock_app = MagicMock()
- mock_app.config = {"HASH_ALGORITHM": algorithm}
-
- with patch("superset.utils.cache_manager.current_app", mock_app):
+ with patch(
+ "superset.utils.cache_manager.get_hash_algorithm", return_value=algorithm
+ ):
hash_obj = configurable_hash_method(b"test_data")
assert hash_obj.hexdigest() == expected_digest