You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/08/06 00:45:14 UTC

(superset) 08/10: fix(reports): guard AUTO currency against sparse-pivot NaN context

This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch fix-reports-text-number-formatting
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7c305e0a9a6536ffa936146f992b09305e9fae65
Author: sadpandajoe <jc...@gmail.com>
AuthorDate: Thu Aug 6 00:38:17 2026 +0000

    fix(reports): guard AUTO currency against sparse-pivot NaN context
    
    A pivot with both `groupbyRows` and `groupbyColumns` has empty cross-product
    cells. `pivot_table` never runs the currency `union` aggregator for a missing
    combination, so it fills those cells with a scalar `NaN` rather than an empty
    tuple. That `NaN` reached `resolve_auto_currency`, whose `list(currency_context)`
    raised `TypeError: 'float' object is not iterable`. Because the call sits outside
    the formatting try/except, it propagated out of the post-processor and failed the
    whole report instead of degrading to the raw value like every other formatting
    path.
    
    Coerce a non-iterable or `NaN` context to an empty context inside
    `resolve_auto_currency` itself, where no caller can bypass it, so AUTO resolution
    follows the existing empty-context path (single currency -> that symbol, mixed ->
    neutral, empty -> detected fallback).
    
    Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
 superset/utils/number_format.py              | 10 +++++++++-
 tests/unit_tests/utils/number_format_test.py | 27 +++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/superset/utils/number_format.py b/superset/utils/number_format.py
index 1a15d44da15..8b3fa120591 100644
--- a/superset/utils/number_format.py
+++ b/superset/utils/number_format.py
@@ -117,7 +117,15 @@ def resolve_auto_currency(
         return currency
 
     if currency_context is not None:
-        context_values = list(currency_context)
+        # A dense pivot cell carries an iterable of currency codes, but a sparse
+        # 2D pivot leaves missing cross-product cells as a scalar ``NaN`` (pandas
+        # never runs the union aggregator for them). Coerce any non-iterable or
+        # NaN context to empty so AUTO resolution follows the empty-context path
+        # instead of raising and taking down the whole report.
+        try:
+            context_values = list(currency_context)
+        except TypeError:
+            context_values = []
         normalized_currencies = {
             normalized
             for value in context_values
diff --git a/tests/unit_tests/utils/number_format_test.py b/tests/unit_tests/utils/number_format_test.py
index 60a6bc7ef34..0a013fdfd54 100644
--- a/tests/unit_tests/utils/number_format_test.py
+++ b/tests/unit_tests/utils/number_format_test.py
@@ -122,6 +122,33 @@ def test_resolve_auto_currency_prefers_cell_context_and_detects_mixed() -> None:
     )
 
 
+def test_resolve_auto_currency_coerces_non_iterable_context() -> None:
+    """
+    A sparse 2D pivot leaves missing cells as scalar ``NaN`` rather than an
+    empty tuple, so AUTO resolution must treat a non-iterable context as empty
+    instead of raising ``TypeError`` on ``list(context)``.
+    """
+    currency = {"symbol": "AUTO", "symbolPosition": "prefix"}
+
+    # NaN context with fallback enabled behaves like an empty context and uses
+    # the query-wide detected currency.
+    assert resolve_auto_currency(currency, "GBP", currency_context=float("nan")) == {
+        "symbol": "GBP",
+        "symbolPosition": "prefix",
+    }
+
+    # NaN context without fallback keeps AUTO, matching the empty-context path.
+    assert (
+        resolve_auto_currency(
+            currency,
+            "GBP",
+            currency_context=float("nan"),
+            fallback_to_detected=False,
+        )
+        is currency
+    )
+
+
 @pytest.mark.parametrize(
     "value,expected",
     [