gabotorresruiz commented on code in PR #45288:
URL: https://github.com/apache/superset/pull/45288#discussion_r4287098170


##########
superset/datasource/api.py:
##########
@@ -672,7 +678,14 @@ def compatible(self, datasource_type: str, datasource_id: 
int) -> FlaskResponse:
             ).hexdigest()
         )
 
-        if (cached := cache_manager.data_cache.get(cache_key)) is not None:
+        timeout: int = datasource.cache_timeout or app.config.get(

Review Comment:
   Not a blocker, more about the operator burden the docs now ask for. The 
`compatible:` key material just above does not include `changed_on`, unlike 
`_column_values_cache_key`, so a pre-existing no-expiry `compatible:` entry 
stays reachable after re-enabling even once the datasource row is edited, while 
the `col_values:` one falls out of its key on that same edit. I confirmed both 
on a real Redis. That makes the manual invalidation mandatory for `compatible:` 
and belt-and-braces for `col_values:`. Would adding `changed_on` here be in 
scope, or better as a follow-up?



##########
docs/admin_docs/configuration/cache.mdx:
##########
@@ -112,6 +112,19 @@ value defined in `DATA_CACHE_CONFIG`.
 Note, that by setting the cache timeout to `-1`, caching for charting data can 
be disabled, either
 per chart, dataset or database, or by default if set in `DATA_CACHE_CONFIG`.
 
+The datasource column-values and compatibility endpoints skip cache reads and 
writes when
+their effective timeout is `-1`. They use the datasource timeout (for SQL 
datasets, falling
+back to the database timeout), then the **top-level** `CACHE_DEFAULT_TIMEOUT` 
when that value
+is unset or zero. They do not use `DATA_CACHE_CONFIG["CACHE_DEFAULT_TIMEOUT"]` 
for this
+fallback, so setting only that nested default to `-1` does not disable these 
endpoints.

Review Comment:
   Not a blocker, but I think this paragraph describes a divergence that is not 
actually there. The chart-data write path resolves the same fallback: 
`set_and_log_cache` substitutes `app.config["CACHE_DEFAULT_TIMEOUT"]` whenever 
the resolved timeout is `None`, and returns early on `-1` 
(`superset/utils/cache.py:76-84`), so 
`DATA_CACHE_CONFIG["CACHE_DEFAULT_TIMEOUT"]` is not the disable lever there 
either. With the nested default at `-1` against a real Redis the chart-data 
write still landed with the top-level TTL rather than being skipped. If that is 
right, these endpoints already match chart data and the alignment follow-up may 
be unnecessary.
   
   Separately, with `-1` set only at the top level, 
`QueryContext.get_cache_timeout()` returns `None` 
(`superset/common/query_context.py:151-155`), so `get_df_payload_result` never 
sets `force_query` and chart data keeps serving existing entries while skipping 
writes. That is the same read-side half you are fixing here. Worth a follow-up, 
or am I misreading that path?



##########
tests/unit_tests/semantic_layers/values_endpoint_test.py:
##########
@@ -577,3 +577,139 @@ def 
test_unversioned_suggestion_cache_material_is_unchanged(
         }
         keys.append(cache.get.call_args.args[0])
     assert keys[0] == keys[1]
+
+
[email protected]("use_default", [False, True])
[email protected]("warm", [False, True])
[email protected]("force", [False, True])
[email protected]("search", [False, True])
+def test_disabled_values_cache_skips_reads_and_writes(
+    client: Any,
+    full_api_access: None,
+    semantic_view_datasource: SemanticView,
+    mocker: MockerFixture,
+    use_default: bool,
+    warm: bool,
+    force: bool,
+    search: bool,
+) -> None:
+    """A disabled effective timeout bypasses warm and cold suggestion 
caches."""
+    from flask import current_app
+
+    from superset.constants import CACHE_DISABLED_TIMEOUT
+
+    semantic_view_datasource.cache_timeout = (
+        None if use_default else CACHE_DISABLED_TIMEOUT
+    )
+    mocker.patch.dict(
+        current_app.config,
+        {"CACHE_DEFAULT_TIMEOUT": CACHE_DISABLED_TIMEOUT if use_default else 
300},
+    )
+    cache: MagicMock = 
mocker.patch("superset.datasource.api.cache_manager").data_cache
+    cache.get.return_value = ["stale"] if warm else None
+    response: Any = _get(
+        client,
+        f"category/values/?force={str(force).lower()}" + ("&q=oo" if search 
else ""),
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == ["Books", "Clothing"]
+    assert response.headers["X-Cache-Status"] == "MISS"
+    cache.get.assert_not_called()
+    cache.set.assert_not_called()
+    implementation: MagicMock = cast(MagicMock, 
semantic_view_datasource.implementation)
+    implementation.get_values.assert_called_once()
+
+
[email protected]("use_default", [False, True])
[email protected]("warm", [False, True])
+def test_disabled_compatible_cache_skips_reads_and_writes(
+    client: Any,
+    full_api_access: None,
+    semantic_view_datasource: SemanticView,
+    mocker: MockerFixture,
+    use_default: bool,
+    warm: bool,
+) -> None:
+    """A disabled compatibility cache must recompute even with a stored 
result."""
+    from flask import current_app
+
+    from superset.constants import CACHE_DISABLED_TIMEOUT
+
+    semantic_view_datasource.cache_timeout = (
+        None if use_default else CACHE_DISABLED_TIMEOUT
+    )
+    mocker.patch.dict(
+        current_app.config,
+        {"CACHE_DEFAULT_TIMEOUT": CACHE_DISABLED_TIMEOUT if use_default else 
300},
+    )
+    metrics: MagicMock = mocker.patch.object(
+        SemanticView, "get_compatible_metrics", return_value=["revenue"]
+    )
+    dimensions: MagicMock = mocker.patch.object(
+        SemanticView, "get_compatible_dimensions", return_value=["category"]
+    )
+    cache: MagicMock = 
mocker.patch("superset.datasource.api.cache_manager").data_cache
+    cache.get.return_value = {"stale": True} if warm else None
+
+    response: Any = client.post(
+        "/api/v1/datasource/semantic_view/1/compatible", json={}
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {
+        "compatible_metrics": ["revenue"],
+        "compatible_dimensions": ["category"],
+    }
+    metrics.assert_called_once_with([], [])
+    dimensions.assert_called_once_with([], [])
+    cache.get.assert_not_called()
+    cache.set.assert_not_called()
+
+
[email protected]("compatible", [False, True])
[email protected](
+    "datasource_timeout, default_timeout, expected_timeout",
+    [(120, 300, 120), (None, 300, 300), (0, 300, 300), (120, -1, 120)],

Review Comment:
   Just a small NIT: could we add a `(None, 0, 0)` row here? A guard written as 
`timeout > 0` instead of `timeout != CACHE_DISABLED_TIMEOUT` passes all 86 
tests in this module as it stands, and that variant silently stops caching when 
`CACHE_DEFAULT_TIMEOUT` is `0`, which Redis reads as no expiry rather than no 
caching. The extra row fails on that variant and passes as written.
   
   ```suggestion
       [(120, 300, 120), (None, 300, 300), (0, 300, 300), (120, -1, 120), 
(None, 0, 0)],
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to