subagents
This commit is contained in:
@@ -35,6 +35,7 @@ class SupersetClient:
|
||||
# @PRE: `env` должен быть валидным объектом Environment.
|
||||
# @POST: Атрибуты `env` и `network` созданы и готовы к работе.
|
||||
# @DATA_CONTRACT: Input[Environment] -> self.network[APIClient]
|
||||
# @RELATION: [DEPENDS_ON] ->[Environment]
|
||||
# @RELATION: [DEPENDS_ON] ->[APIClient]
|
||||
def __init__(self, env: Environment):
|
||||
with belief_scope("__init__"):
|
||||
@@ -311,7 +312,7 @@ class SupersetClient:
|
||||
})
|
||||
|
||||
return total_count, result
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient.get_dashboards_summary_page:Function]
|
||||
# [/DEF:SupersetClient.get_dashboards_summary_page:Function]
|
||||
|
||||
# [DEF:SupersetClient._extract_owner_labels:Function]
|
||||
# @COMPLEXITY: 1
|
||||
@@ -368,9 +369,9 @@ class SupersetClient:
|
||||
if email:
|
||||
return email
|
||||
return None
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient._extract_user_display:Function]
|
||||
# [/DEF:SupersetClient._extract_user_display:Function]
|
||||
|
||||
# [DEF:backend.src.core.superset_client.SupersetClient._sanitize_user_text:Function]
|
||||
# [DEF:SupersetClient._sanitize_user_text:Function]
|
||||
# @COMPLEXITY: 1
|
||||
# @PURPOSE: Convert scalar value to non-empty user-facing text.
|
||||
# @PRE: value can be any scalar type.
|
||||
@@ -382,7 +383,7 @@ class SupersetClient:
|
||||
if not normalized:
|
||||
return None
|
||||
return normalized
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient._sanitize_user_text:Function]
|
||||
# [/DEF:SupersetClient._sanitize_user_text:Function]
|
||||
|
||||
# [DEF:SupersetClient.get_dashboard:Function]
|
||||
# @COMPLEXITY: 3
|
||||
@@ -413,6 +414,206 @@ class SupersetClient:
|
||||
return cast(Dict, response)
|
||||
# [/DEF:SupersetClient.get_dashboard_permalink_state:Function]
|
||||
|
||||
# [DEF:SupersetClient.get_native_filter_state:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Fetches stored native filter state by filter state key.
|
||||
# @PRE: Client is authenticated and filter_state_key exists.
|
||||
# @POST: Returns native filter state payload from Superset API.
|
||||
# @DATA_CONTRACT: Input[dashboard_id: Union[int, str], filter_state_key: str] -> Output[Dict]
|
||||
# @RELATION: [CALLS] ->[APIClient.request]
|
||||
def get_native_filter_state(self, dashboard_id: Union[int, str], filter_state_key: str) -> Dict:
|
||||
with belief_scope("SupersetClient.get_native_filter_state", f"dashboard={dashboard_id}, key={filter_state_key}"):
|
||||
response = self.network.request(
|
||||
method="GET",
|
||||
endpoint=f"/dashboard/{dashboard_id}/filter_state/{filter_state_key}"
|
||||
)
|
||||
return cast(Dict, response)
|
||||
# [/DEF:SupersetClient.get_native_filter_state:Function]
|
||||
|
||||
# [DEF:SupersetClient.extract_native_filters_from_permalink:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Extract native filters dataMask from a permalink key.
|
||||
# @PRE: Client is authenticated and permalink_key exists.
|
||||
# @POST: Returns extracted dataMask with filter states.
|
||||
# @DATA_CONTRACT: Input[permalink_key: str] -> Output[Dict]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.get_dashboard_permalink_state]
|
||||
def extract_native_filters_from_permalink(self, permalink_key: str) -> Dict:
|
||||
with belief_scope("SupersetClient.extract_native_filters_from_permalink", f"key={permalink_key}"):
|
||||
permalink_response = self.get_dashboard_permalink_state(permalink_key)
|
||||
|
||||
# Permalink response structure: { "result": { "state": { "dataMask": {...}, ... } } }
|
||||
# or directly: { "state": { "dataMask": {...}, ... } }
|
||||
result = permalink_response.get("result", permalink_response)
|
||||
state = result.get("state", result)
|
||||
data_mask = state.get("dataMask", {})
|
||||
|
||||
extracted_filters = {}
|
||||
for filter_id, filter_data in data_mask.items():
|
||||
if not isinstance(filter_data, dict):
|
||||
continue
|
||||
extracted_filters[filter_id] = {
|
||||
"extraFormData": filter_data.get("extraFormData", {}),
|
||||
"filterState": filter_data.get("filterState", {}),
|
||||
"ownState": filter_data.get("ownState", {}),
|
||||
}
|
||||
|
||||
return {
|
||||
"dataMask": extracted_filters,
|
||||
"activeTabs": state.get("activeTabs", []),
|
||||
"anchor": state.get("anchor"),
|
||||
"chartStates": state.get("chartStates", {}),
|
||||
"permalink_key": permalink_key,
|
||||
}
|
||||
# [/DEF:SupersetClient.extract_native_filters_from_permalink:Function]
|
||||
|
||||
# [DEF:SupersetClient.extract_native_filters_from_key:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Extract native filters from a native_filters_key URL parameter.
|
||||
# @PRE: Client is authenticated, dashboard_id and filter_state_key exist.
|
||||
# @POST: Returns extracted filter state with extraFormData.
|
||||
# @DATA_CONTRACT: Input[dashboard_id: Union[int, str], filter_state_key: str] -> Output[Dict]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.get_native_filter_state]
|
||||
def extract_native_filters_from_key(self, dashboard_id: Union[int, str], filter_state_key: str) -> Dict:
|
||||
with belief_scope("SupersetClient.extract_native_filters_from_key", f"dashboard={dashboard_id}, key={filter_state_key}"):
|
||||
filter_response = self.get_native_filter_state(dashboard_id, filter_state_key)
|
||||
|
||||
# Filter state response structure: { "result": { "value": "{...json...}" } }
|
||||
# or: { "value": "{...json...}" }
|
||||
result = filter_response.get("result", filter_response)
|
||||
value = result.get("value")
|
||||
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
parsed_value = json.loads(value)
|
||||
except json.JSONDecodeError as e:
|
||||
app_logger.warning("[extract_native_filters_from_key][Warning] Failed to parse filter state JSON: %s", e)
|
||||
parsed_value = {}
|
||||
elif isinstance(value, dict):
|
||||
parsed_value = value
|
||||
else:
|
||||
parsed_value = {}
|
||||
|
||||
# The parsed value contains filter state with structure:
|
||||
# { "filter_id": { "id": "...", "extraFormData": {...}, "filterState": {...} } }
|
||||
# or a single filter: { "id": "...", "extraFormData": {...}, "filterState": {...} }
|
||||
extracted_filters = {}
|
||||
|
||||
if "id" in parsed_value and "extraFormData" in parsed_value:
|
||||
# Single filter format
|
||||
filter_id = parsed_value.get("id", filter_state_key)
|
||||
extracted_filters[filter_id] = {
|
||||
"extraFormData": parsed_value.get("extraFormData", {}),
|
||||
"filterState": parsed_value.get("filterState", {}),
|
||||
"ownState": parsed_value.get("ownState", {}),
|
||||
}
|
||||
else:
|
||||
# Multiple filters format
|
||||
for filter_id, filter_data in parsed_value.items():
|
||||
if not isinstance(filter_data, dict):
|
||||
continue
|
||||
extracted_filters[filter_id] = {
|
||||
"extraFormData": filter_data.get("extraFormData", {}),
|
||||
"filterState": filter_data.get("filterState", {}),
|
||||
"ownState": filter_data.get("ownState", {}),
|
||||
}
|
||||
|
||||
return {
|
||||
"dataMask": extracted_filters,
|
||||
"dashboard_id": dashboard_id,
|
||||
"filter_state_key": filter_state_key,
|
||||
}
|
||||
# [/DEF:SupersetClient.extract_native_filters_from_key:Function]
|
||||
|
||||
# [DEF:SupersetClient.parse_dashboard_url_for_filters:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Parse a Superset dashboard URL and extract native filter state if present.
|
||||
# @PRE: url must be a valid Superset dashboard URL with optional permalink or native_filters_key.
|
||||
# @POST: Returns extracted filter state or empty dict if no filters found.
|
||||
# @DATA_CONTRACT: Input[url: str] -> Output[Dict]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.extract_native_filters_from_permalink]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.extract_native_filters_from_key]
|
||||
def parse_dashboard_url_for_filters(self, url: str) -> Dict:
|
||||
with belief_scope("SupersetClient.parse_dashboard_url_for_filters", f"url={url}"):
|
||||
import urllib.parse
|
||||
|
||||
parsed_url = urllib.parse.urlparse(url)
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
path_parts = parsed_url.path.rstrip("/").split("/")
|
||||
|
||||
result = {
|
||||
"url": url,
|
||||
"dashboard_id": None,
|
||||
"filter_type": None,
|
||||
"filters": {},
|
||||
}
|
||||
|
||||
# Check for permalink URL: /dashboard/p/{key}/ or /superset/dashboard/p/{key}/
|
||||
if "p" in path_parts:
|
||||
try:
|
||||
p_index = path_parts.index("p")
|
||||
if p_index + 1 < len(path_parts):
|
||||
permalink_key = path_parts[p_index + 1]
|
||||
filter_data = self.extract_native_filters_from_permalink(permalink_key)
|
||||
result["filter_type"] = "permalink"
|
||||
result["filters"] = filter_data
|
||||
return result
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Check for native_filters_key in query params
|
||||
native_filters_key = query_params.get("native_filters_key", [None])[0]
|
||||
if native_filters_key:
|
||||
# Extract dashboard ID or slug from URL path
|
||||
dashboard_ref = None
|
||||
if "dashboard" in path_parts:
|
||||
try:
|
||||
dash_index = path_parts.index("dashboard")
|
||||
if dash_index + 1 < len(path_parts):
|
||||
potential_id = path_parts[dash_index + 1]
|
||||
# Skip if it's a reserved word
|
||||
if potential_id not in ("p", "list", "new"):
|
||||
dashboard_ref = potential_id
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if dashboard_ref:
|
||||
# Resolve slug to numeric ID — the filter_state API requires a numeric ID
|
||||
resolved_id = None
|
||||
try:
|
||||
resolved_id = int(dashboard_ref)
|
||||
except (ValueError, TypeError):
|
||||
try:
|
||||
dash_resp = self.get_dashboard(dashboard_ref)
|
||||
dash_data = dash_resp.get("result", dash_resp) if isinstance(dash_resp, dict) else {}
|
||||
raw_id = dash_data.get("id")
|
||||
if raw_id is not None:
|
||||
resolved_id = int(raw_id)
|
||||
except Exception as e:
|
||||
app_logger.warning("[parse_dashboard_url_for_filters][Warning] Failed to resolve dashboard slug '%s' to ID: %s", dashboard_ref, e)
|
||||
|
||||
if resolved_id is not None:
|
||||
filter_data = self.extract_native_filters_from_key(resolved_id, native_filters_key)
|
||||
result["filter_type"] = "native_filters_key"
|
||||
result["dashboard_id"] = resolved_id
|
||||
result["filters"] = filter_data
|
||||
return result
|
||||
else:
|
||||
app_logger.warning("[parse_dashboard_url_for_filters][Warning] Could not resolve dashboard_id from URL for native_filters_key")
|
||||
|
||||
# Check for native_filters in query params (direct filter values)
|
||||
native_filters = query_params.get("native_filters", [None])[0]
|
||||
if native_filters:
|
||||
try:
|
||||
parsed_filters = json.loads(native_filters)
|
||||
result["filter_type"] = "native_filters"
|
||||
result["filters"] = {"dataMask": parsed_filters}
|
||||
return result
|
||||
except json.JSONDecodeError as e:
|
||||
app_logger.warning("[parse_dashboard_url_for_filters][Warning] Failed to parse native_filters JSON: %s", e)
|
||||
|
||||
return result
|
||||
# [/DEF:SupersetClient.parse_dashboard_url_for_filters:Function]
|
||||
|
||||
# [DEF:SupersetClient.get_chart:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Fetches a single chart by ID.
|
||||
@@ -911,13 +1112,13 @@ class SupersetClient:
|
||||
return result
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient.get_dataset_detail:Function]
|
||||
|
||||
# [DEF:backend.src.core.superset_client.SupersetClient.get_dataset:Function]
|
||||
# [DEF:SupersetClient.get_dataset:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Получает информацию о конкретном датасете по его ID.
|
||||
# @PRE: dataset_id must exist.
|
||||
# @POST: Returns dataset details.
|
||||
# @DATA_CONTRACT: Input[dataset_id: int] -> Output[Dict]
|
||||
# @RELATION: [CALLS] ->[backend.src.core.utils.network.APIClient.request]
|
||||
# @RELATION: [CALLS] ->[APIClient.request]
|
||||
def get_dataset(self, dataset_id: int) -> Dict:
|
||||
with belief_scope("SupersetClient.get_dataset", f"id={dataset_id}"):
|
||||
app_logger.info("[get_dataset][Enter] Fetching dataset %s.", dataset_id)
|
||||
@@ -925,19 +1126,20 @@ class SupersetClient:
|
||||
response = cast(Dict, response)
|
||||
app_logger.info("[get_dataset][Exit] Got dataset %s.", dataset_id)
|
||||
return response
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient.get_dataset:Function]
|
||||
# [/DEF:SupersetClient.get_dataset:Function]
|
||||
|
||||
# [DEF:SupersetClient.compile_dataset_preview:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Compile dataset preview SQL through the real Superset chart-data endpoint and return normalized SQL output.
|
||||
# @PURPOSE: Compile dataset preview SQL through the strongest supported Superset preview endpoint family and return normalized SQL output.
|
||||
# @PRE: dataset_id must be valid and template_params/effective_filters must represent the current preview session inputs.
|
||||
# @POST: Returns normalized compiled SQL plus raw upstream response without guessing unsupported endpoints.
|
||||
# @POST: Returns normalized compiled SQL plus raw upstream response, preferring legacy form_data transport with explicit fallback to chart-data.
|
||||
# @DATA_CONTRACT: Input[dataset_id:int, template_params:Dict, effective_filters:List[Dict]] -> Output[Dict[str, Any]]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.get_dataset]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.build_dataset_preview_query_context]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.build_dataset_preview_legacy_form_data]
|
||||
# @RELATION: [CALLS] ->[APIClient.request]
|
||||
# @RELATION: [CALLS] ->[SupersetClient._extract_compiled_sql_from_chart_data_response]
|
||||
# @SIDE_EFFECT: Performs upstream dataset lookup and chart-data network I/O against Superset.
|
||||
# @RELATION: [CALLS] ->[SupersetClient._extract_compiled_sql_from_preview_response]
|
||||
# @SIDE_EFFECT: Performs upstream dataset lookup and preview network I/O against Superset.
|
||||
def compile_dataset_preview(
|
||||
self,
|
||||
dataset_id: int,
|
||||
@@ -945,14 +1147,6 @@ class SupersetClient:
|
||||
effective_filters: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
with belief_scope("SupersetClient.compile_dataset_preview", f"id={dataset_id}"):
|
||||
app_logger.reason(
|
||||
"Compiling dataset preview via Superset chart-data endpoint",
|
||||
extra={
|
||||
"dataset_id": dataset_id,
|
||||
"template_param_count": len(template_params or {}),
|
||||
"filter_count": len(effective_filters or []),
|
||||
},
|
||||
)
|
||||
dataset_response = self.get_dataset(dataset_id)
|
||||
dataset_record = dataset_response.get("result", dataset_response) if isinstance(dataset_response, dict) else {}
|
||||
query_context = self.build_dataset_preview_query_context(
|
||||
@@ -961,31 +1155,197 @@ class SupersetClient:
|
||||
template_params=template_params or {},
|
||||
effective_filters=effective_filters or [],
|
||||
)
|
||||
response = self.network.request(
|
||||
method="POST",
|
||||
endpoint="/chart/data",
|
||||
data=json.dumps(query_context),
|
||||
headers={"Content-Type": "application/json"},
|
||||
legacy_form_data = self.build_dataset_preview_legacy_form_data(
|
||||
dataset_id=dataset_id,
|
||||
dataset_record=dataset_record,
|
||||
template_params=template_params or {},
|
||||
effective_filters=effective_filters or [],
|
||||
)
|
||||
normalized = self._extract_compiled_sql_from_chart_data_response(response)
|
||||
normalized["query_context"] = query_context
|
||||
legacy_form_data_payload = json.dumps(legacy_form_data, sort_keys=True, default=str)
|
||||
request_payload = json.dumps(query_context)
|
||||
strategy_attempts: List[Dict[str, Any]] = []
|
||||
strategy_candidates: List[Dict[str, Any]] = [
|
||||
{
|
||||
"endpoint_kind": "legacy_explore_form_data",
|
||||
"endpoint": "/explore_json/form_data",
|
||||
"request_transport": "query_param_form_data",
|
||||
"params": {"form_data": legacy_form_data_payload},
|
||||
},
|
||||
{
|
||||
"endpoint_kind": "legacy_data_form_data",
|
||||
"endpoint": "/data",
|
||||
"request_transport": "query_param_form_data",
|
||||
"params": {"form_data": legacy_form_data_payload},
|
||||
},
|
||||
{
|
||||
"endpoint_kind": "v1_chart_data",
|
||||
"endpoint": "/chart/data",
|
||||
"request_transport": "json_body",
|
||||
"data": request_payload,
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
},
|
||||
]
|
||||
|
||||
for candidate in strategy_candidates:
|
||||
endpoint_kind = candidate["endpoint_kind"]
|
||||
endpoint_path = candidate["endpoint"]
|
||||
request_transport = candidate["request_transport"]
|
||||
request_params = deepcopy(candidate.get("params") or {})
|
||||
request_body = candidate.get("data")
|
||||
request_headers = deepcopy(candidate.get("headers") or {})
|
||||
request_param_keys = sorted(request_params.keys())
|
||||
request_payload_keys: List[str] = []
|
||||
if isinstance(request_body, str):
|
||||
try:
|
||||
decoded_request_body = json.loads(request_body)
|
||||
if isinstance(decoded_request_body, dict):
|
||||
request_payload_keys = sorted(decoded_request_body.keys())
|
||||
except json.JSONDecodeError:
|
||||
request_payload_keys = []
|
||||
elif isinstance(request_body, dict):
|
||||
request_payload_keys = sorted(request_body.keys())
|
||||
|
||||
strategy_diagnostics = {
|
||||
"endpoint": endpoint_path,
|
||||
"endpoint_kind": endpoint_kind,
|
||||
"request_transport": request_transport,
|
||||
"contains_root_datasource": endpoint_kind == "v1_chart_data" and "datasource" in query_context,
|
||||
"contains_form_datasource": endpoint_kind.startswith("legacy_") and "datasource" in legacy_form_data,
|
||||
"contains_query_object_datasource": bool(query_context.get("queries")) and isinstance(query_context["queries"][0], dict) and "datasource" in query_context["queries"][0],
|
||||
"request_param_keys": request_param_keys,
|
||||
"request_payload_keys": request_payload_keys,
|
||||
}
|
||||
app_logger.reason(
|
||||
"Attempting Superset dataset preview compilation strategy",
|
||||
extra={
|
||||
"dataset_id": dataset_id,
|
||||
**strategy_diagnostics,
|
||||
"request_params": request_params,
|
||||
"request_payload": request_body,
|
||||
"legacy_form_data": legacy_form_data if endpoint_kind.startswith("legacy_") else None,
|
||||
"query_context": query_context if endpoint_kind == "v1_chart_data" else None,
|
||||
"template_param_count": len(template_params or {}),
|
||||
"filter_count": len(effective_filters or []),
|
||||
},
|
||||
)
|
||||
try:
|
||||
response = self.network.request(
|
||||
method="POST",
|
||||
endpoint=endpoint_path,
|
||||
params=request_params or None,
|
||||
data=request_body,
|
||||
headers=request_headers or None,
|
||||
)
|
||||
normalized = self._extract_compiled_sql_from_preview_response(response)
|
||||
normalized["query_context"] = query_context
|
||||
normalized["legacy_form_data"] = legacy_form_data
|
||||
normalized["endpoint"] = endpoint_path
|
||||
normalized["endpoint_kind"] = endpoint_kind
|
||||
normalized["dataset_id"] = dataset_id
|
||||
normalized["strategy_attempts"] = strategy_attempts + [
|
||||
{
|
||||
**strategy_diagnostics,
|
||||
"success": True,
|
||||
}
|
||||
]
|
||||
app_logger.reflect(
|
||||
"Dataset preview compilation returned normalized SQL payload",
|
||||
extra={
|
||||
"dataset_id": dataset_id,
|
||||
**strategy_diagnostics,
|
||||
"success": True,
|
||||
"compiled_sql_length": len(str(normalized.get("compiled_sql") or "")),
|
||||
"response_diagnostics": normalized.get("response_diagnostics"),
|
||||
},
|
||||
)
|
||||
return normalized
|
||||
except Exception as exc:
|
||||
failure_diagnostics = {
|
||||
**strategy_diagnostics,
|
||||
"success": False,
|
||||
"error": str(exc),
|
||||
}
|
||||
strategy_attempts.append(failure_diagnostics)
|
||||
app_logger.explore(
|
||||
"Superset dataset preview compilation strategy failed",
|
||||
extra={
|
||||
"dataset_id": dataset_id,
|
||||
**failure_diagnostics,
|
||||
"request_params": request_params,
|
||||
"request_payload": request_body,
|
||||
},
|
||||
)
|
||||
|
||||
raise SupersetAPIError(
|
||||
"Superset preview compilation failed for all known strategies "
|
||||
f"(attempts={strategy_attempts!r})"
|
||||
)
|
||||
# [/DEF:SupersetClient.compile_dataset_preview:Function]
|
||||
|
||||
# [DEF:SupersetClient.build_dataset_preview_legacy_form_data:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Build browser-style legacy form_data payload for Superset preview endpoints inferred from observed deployment traffic.
|
||||
# @PRE: dataset_record should come from Superset dataset detail when possible.
|
||||
# @POST: Returns one serialized-ready form_data structure preserving native filter clauses in legacy transport fields.
|
||||
# @DATA_CONTRACT: Input[dataset_id:int,dataset_record:Dict,template_params:Dict,effective_filters:List[Dict]] -> Output[Dict[str, Any]]
|
||||
# @RELATION: [CALLS] ->[SupersetClient.build_dataset_preview_query_context]
|
||||
# @SIDE_EFFECT: Emits reasoning diagnostics describing the inferred legacy payload shape.
|
||||
def build_dataset_preview_legacy_form_data(
|
||||
self,
|
||||
dataset_id: int,
|
||||
dataset_record: Dict[str, Any],
|
||||
template_params: Dict[str, Any],
|
||||
effective_filters: List[Dict[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
with belief_scope("SupersetClient.build_dataset_preview_legacy_form_data", f"id={dataset_id}"):
|
||||
query_context = self.build_dataset_preview_query_context(
|
||||
dataset_id=dataset_id,
|
||||
dataset_record=dataset_record,
|
||||
template_params=template_params,
|
||||
effective_filters=effective_filters,
|
||||
)
|
||||
query_object = deepcopy(query_context.get("queries", [{}])[0] if query_context.get("queries") else {})
|
||||
legacy_form_data = deepcopy(query_context.get("form_data", {}))
|
||||
legacy_form_data.pop("datasource", None)
|
||||
legacy_form_data["metrics"] = deepcopy(query_object.get("metrics", ["count"]))
|
||||
legacy_form_data["columns"] = deepcopy(query_object.get("columns", []))
|
||||
legacy_form_data["orderby"] = deepcopy(query_object.get("orderby", []))
|
||||
legacy_form_data["annotation_layers"] = deepcopy(query_object.get("annotation_layers", []))
|
||||
legacy_form_data["row_limit"] = query_object.get("row_limit", 1000)
|
||||
legacy_form_data["series_limit"] = query_object.get("series_limit", 0)
|
||||
legacy_form_data["url_params"] = deepcopy(query_object.get("url_params", template_params))
|
||||
legacy_form_data["applied_time_extras"] = deepcopy(query_object.get("applied_time_extras", {}))
|
||||
legacy_form_data["result_format"] = query_context.get("result_format", "json")
|
||||
legacy_form_data["result_type"] = query_context.get("result_type", "query")
|
||||
legacy_form_data["force"] = bool(query_context.get("force", True))
|
||||
extras = query_object.get("extras")
|
||||
if isinstance(extras, dict):
|
||||
legacy_form_data["extras"] = deepcopy(extras)
|
||||
time_range = query_object.get("time_range")
|
||||
if time_range:
|
||||
legacy_form_data["time_range"] = time_range
|
||||
|
||||
app_logger.reflect(
|
||||
"Dataset preview compilation returned normalized SQL payload",
|
||||
"Built Superset legacy preview form_data payload from browser-observed request shape",
|
||||
extra={
|
||||
"dataset_id": dataset_id,
|
||||
"compiled_sql_length": len(str(normalized.get("compiled_sql") or "")),
|
||||
"legacy_endpoint_inference": "POST /explore_json/form_data?form_data=... primary, POST /data?form_data=... fallback, based on observed browser traffic",
|
||||
"contains_form_datasource": "datasource" in legacy_form_data,
|
||||
"legacy_form_data_keys": sorted(legacy_form_data.keys()),
|
||||
"legacy_extra_filters": legacy_form_data.get("extra_filters", []),
|
||||
"legacy_extra_form_data": legacy_form_data.get("extra_form_data", {}),
|
||||
},
|
||||
)
|
||||
return normalized
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient.compile_dataset_preview:Function]
|
||||
return legacy_form_data
|
||||
# [/DEF:SupersetClient.build_dataset_preview_legacy_form_data:Function]
|
||||
|
||||
# [DEF:backend.src.core.superset_client.SupersetClient.build_dataset_preview_query_context:Function]
|
||||
# [DEF:SupersetClient.build_dataset_preview_query_context:Function]
|
||||
# @COMPLEXITY: 4
|
||||
# @PURPOSE: Build a reduced-scope chart-data query context for deterministic dataset preview compilation.
|
||||
# @PRE: dataset_record should come from Superset dataset detail when possible.
|
||||
# @POST: Returns an explicit chart-data payload based on current session inputs and dataset metadata.
|
||||
# @DATA_CONTRACT: Input[dataset_id:int,dataset_record:Dict,template_params:Dict,effective_filters:List[Dict]] -> Output[Dict[str, Any]]
|
||||
# @RELATION: [CALLS] ->[backend.src.core.superset_client.SupersetClient._normalize_effective_filters_for_query_context]
|
||||
# @RELATION: [CALLS] ->[SupersetClient._normalize_effective_filters_for_query_context]
|
||||
# @SIDE_EFFECT: Emits reasoning and reflection logs for deterministic preview payload construction.
|
||||
def build_dataset_preview_query_context(
|
||||
self,
|
||||
@@ -996,7 +1356,9 @@ class SupersetClient:
|
||||
) -> Dict[str, Any]:
|
||||
with belief_scope("SupersetClient.build_dataset_preview_query_context", f"id={dataset_id}"):
|
||||
normalized_template_params = deepcopy(template_params or {})
|
||||
normalized_filters = self._normalize_effective_filters_for_query_context(effective_filters or [])
|
||||
normalized_filter_payload = self._normalize_effective_filters_for_query_context(effective_filters or [])
|
||||
normalized_filters = normalized_filter_payload["filters"]
|
||||
normalized_extra_form_data = normalized_filter_payload["extra_form_data"]
|
||||
|
||||
datasource_payload: Dict[str, Any] = {
|
||||
"id": dataset_id,
|
||||
@@ -1011,6 +1373,23 @@ class SupersetClient:
|
||||
if datasource_type:
|
||||
datasource_payload["type"] = datasource_type
|
||||
|
||||
serialized_dataset_template_params = dataset_record.get("template_params")
|
||||
if isinstance(serialized_dataset_template_params, str) and serialized_dataset_template_params.strip():
|
||||
try:
|
||||
parsed_dataset_template_params = json.loads(serialized_dataset_template_params)
|
||||
if isinstance(parsed_dataset_template_params, dict):
|
||||
for key, value in parsed_dataset_template_params.items():
|
||||
normalized_template_params.setdefault(str(key), value)
|
||||
except json.JSONDecodeError:
|
||||
app_logger.explore(
|
||||
"Dataset template_params could not be parsed while building preview query context",
|
||||
extra={"dataset_id": dataset_id},
|
||||
)
|
||||
|
||||
extra_form_data: Dict[str, Any] = deepcopy(normalized_extra_form_data)
|
||||
if normalized_filters:
|
||||
extra_form_data["filters"] = deepcopy(normalized_filters)
|
||||
|
||||
query_object: Dict[str, Any] = {
|
||||
"filters": normalized_filters,
|
||||
"extras": {"where": ""},
|
||||
@@ -1021,33 +1400,56 @@ class SupersetClient:
|
||||
"row_limit": 1000,
|
||||
"series_limit": 0,
|
||||
"url_params": normalized_template_params,
|
||||
"custom_params": normalized_template_params,
|
||||
"applied_time_extras": {},
|
||||
"result_type": "query",
|
||||
}
|
||||
|
||||
schema = dataset_record.get("schema")
|
||||
if schema:
|
||||
query_object["schema"] = schema
|
||||
|
||||
time_range = dataset_record.get("default_time_range")
|
||||
time_range = extra_form_data.get("time_range") or dataset_record.get("default_time_range")
|
||||
if time_range:
|
||||
query_object["time_range"] = time_range
|
||||
extra_form_data["time_range"] = time_range
|
||||
|
||||
result_format = dataset_record.get("result_format") or "json"
|
||||
result_type = dataset_record.get("result_type") or "full"
|
||||
result_type = "query"
|
||||
|
||||
return {
|
||||
form_data: Dict[str, Any] = {
|
||||
"datasource": f"{datasource_payload['id']}__{datasource_payload['type']}",
|
||||
"datasource_id": datasource_payload["id"],
|
||||
"datasource_type": datasource_payload["type"],
|
||||
"viz_type": "table",
|
||||
"slice_id": None,
|
||||
"query_mode": "raw",
|
||||
"url_params": normalized_template_params,
|
||||
"extra_filters": deepcopy(normalized_filters),
|
||||
"adhoc_filters": [],
|
||||
}
|
||||
if extra_form_data:
|
||||
form_data["extra_form_data"] = extra_form_data
|
||||
|
||||
payload = {
|
||||
"datasource": datasource_payload,
|
||||
"queries": [query_object],
|
||||
"form_data": {
|
||||
"datasource": f"{datasource_payload['id']}__{datasource_payload['type']}",
|
||||
"viz_type": "table",
|
||||
"slice_id": None,
|
||||
"query_mode": "raw",
|
||||
"url_params": normalized_template_params,
|
||||
},
|
||||
"form_data": form_data,
|
||||
"result_format": result_format,
|
||||
"result_type": result_type,
|
||||
"force": True,
|
||||
}
|
||||
app_logger.reflect(
|
||||
"Built Superset dataset preview query context",
|
||||
extra={
|
||||
"dataset_id": dataset_id,
|
||||
"datasource": datasource_payload,
|
||||
"normalized_effective_filters": normalized_filters,
|
||||
"normalized_filter_diagnostics": normalized_filter_payload["diagnostics"],
|
||||
"result_type": result_type,
|
||||
"result_format": result_format,
|
||||
},
|
||||
)
|
||||
return payload
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient.build_dataset_preview_query_context:Function]
|
||||
|
||||
# [DEF:backend.src.core.superset_client.SupersetClient._normalize_effective_filters_for_query_context:Function]
|
||||
@@ -1058,56 +1460,170 @@ class SupersetClient:
|
||||
def _normalize_effective_filters_for_query_context(
|
||||
self,
|
||||
effective_filters: List[Dict[str, Any]],
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> Dict[str, Any]:
|
||||
with belief_scope("SupersetClient._normalize_effective_filters_for_query_context"):
|
||||
normalized_filters: List[Dict[str, Any]] = []
|
||||
merged_extra_form_data: Dict[str, Any] = {}
|
||||
diagnostics: List[Dict[str, Any]] = []
|
||||
|
||||
for item in effective_filters:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
column = str(item.get("variable_name") or item.get("filter_name") or "").strip()
|
||||
if not column:
|
||||
continue
|
||||
value = item.get("effective_value")
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
operator = "IN" if isinstance(value, list) else "=="
|
||||
normalized_filters.append(
|
||||
display_name = str(
|
||||
item.get("display_name")
|
||||
or item.get("filter_name")
|
||||
or item.get("variable_name")
|
||||
or "unresolved_filter"
|
||||
).strip()
|
||||
value = item.get("effective_value")
|
||||
normalized_payload = item.get("normalized_filter_payload")
|
||||
preserved_clauses: List[Dict[str, Any]] = []
|
||||
preserved_extra_form_data: Dict[str, Any] = {}
|
||||
used_preserved_clauses = False
|
||||
|
||||
if isinstance(normalized_payload, dict):
|
||||
raw_clauses = normalized_payload.get("filter_clauses")
|
||||
if isinstance(raw_clauses, list):
|
||||
preserved_clauses = [
|
||||
deepcopy(clause)
|
||||
for clause in raw_clauses
|
||||
if isinstance(clause, dict)
|
||||
]
|
||||
raw_extra_form_data = normalized_payload.get("extra_form_data")
|
||||
if isinstance(raw_extra_form_data, dict):
|
||||
preserved_extra_form_data = deepcopy(raw_extra_form_data)
|
||||
|
||||
if isinstance(preserved_extra_form_data, dict):
|
||||
for key, extra_value in preserved_extra_form_data.items():
|
||||
if key == "filters":
|
||||
continue
|
||||
merged_extra_form_data[key] = deepcopy(extra_value)
|
||||
|
||||
outgoing_clauses: List[Dict[str, Any]] = []
|
||||
if preserved_clauses:
|
||||
for clause in preserved_clauses:
|
||||
clause_copy = deepcopy(clause)
|
||||
if "val" not in clause_copy and value is not None:
|
||||
clause_copy["val"] = deepcopy(value)
|
||||
outgoing_clauses.append(clause_copy)
|
||||
used_preserved_clauses = True
|
||||
elif preserved_extra_form_data:
|
||||
outgoing_clauses = []
|
||||
else:
|
||||
column = str(item.get("variable_name") or item.get("filter_name") or "").strip()
|
||||
if column and value is not None:
|
||||
operator = "IN" if isinstance(value, list) else "=="
|
||||
outgoing_clauses.append(
|
||||
{
|
||||
"col": column,
|
||||
"op": operator,
|
||||
"val": value,
|
||||
}
|
||||
)
|
||||
|
||||
normalized_filters.extend(outgoing_clauses)
|
||||
diagnostics.append(
|
||||
{
|
||||
"col": column,
|
||||
"op": operator,
|
||||
"val": value,
|
||||
"filter_name": display_name,
|
||||
"value_origin": (
|
||||
normalized_payload.get("value_origin")
|
||||
if isinstance(normalized_payload, dict)
|
||||
else None
|
||||
),
|
||||
"used_preserved_clauses": used_preserved_clauses,
|
||||
"outgoing_clauses": deepcopy(outgoing_clauses),
|
||||
}
|
||||
)
|
||||
return normalized_filters
|
||||
app_logger.reason(
|
||||
"Normalized effective preview filter for Superset query context",
|
||||
extra={
|
||||
"filter_name": display_name,
|
||||
"used_preserved_clauses": used_preserved_clauses,
|
||||
"outgoing_clauses": outgoing_clauses,
|
||||
"value_origin": (
|
||||
normalized_payload.get("value_origin")
|
||||
if isinstance(normalized_payload, dict)
|
||||
else "heuristic_reconstruction"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
"filters": normalized_filters,
|
||||
"extra_form_data": merged_extra_form_data,
|
||||
"diagnostics": diagnostics,
|
||||
}
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient._normalize_effective_filters_for_query_context:Function]
|
||||
|
||||
# [DEF:backend.src.core.superset_client.SupersetClient._extract_compiled_sql_from_chart_data_response:Function]
|
||||
# [DEF:backend.src.core.superset_client.SupersetClient._extract_compiled_sql_from_preview_response:Function]
|
||||
# @COMPLEXITY: 3
|
||||
# @PURPOSE: Normalize compiled SQL from a chart-data response by reading result[].query fields first.
|
||||
# @PRE: response must be the decoded response body from /api/v1/chart/data.
|
||||
# @PURPOSE: Normalize compiled SQL from either chart-data or legacy form_data preview responses.
|
||||
# @PRE: response must be the decoded preview response body from a supported Superset endpoint.
|
||||
# @POST: Returns compiled SQL and raw response or raises SupersetAPIError when the endpoint does not expose query text.
|
||||
def _extract_compiled_sql_from_chart_data_response(self, response: Any) -> Dict[str, Any]:
|
||||
with belief_scope("SupersetClient._extract_compiled_sql_from_chart_data_response"):
|
||||
def _extract_compiled_sql_from_preview_response(self, response: Any) -> Dict[str, Any]:
|
||||
with belief_scope("SupersetClient._extract_compiled_sql_from_preview_response"):
|
||||
if not isinstance(response, dict):
|
||||
raise SupersetAPIError("Superset chart/data response was not a JSON object")
|
||||
raise SupersetAPIError("Superset preview response was not a JSON object")
|
||||
|
||||
response_diagnostics: List[Dict[str, Any]] = []
|
||||
result_payload = response.get("result")
|
||||
if not isinstance(result_payload, list):
|
||||
raise SupersetAPIError("Superset chart/data response did not include a result list")
|
||||
if isinstance(result_payload, list):
|
||||
for index, item in enumerate(result_payload):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
compiled_sql = str(item.get("query") or item.get("sql") or item.get("compiled_sql") or "").strip()
|
||||
response_diagnostics.append(
|
||||
{
|
||||
"index": index,
|
||||
"status": item.get("status"),
|
||||
"applied_filters": item.get("applied_filters"),
|
||||
"rejected_filters": item.get("rejected_filters"),
|
||||
"has_query": bool(compiled_sql),
|
||||
"source": "result_list",
|
||||
}
|
||||
)
|
||||
if compiled_sql:
|
||||
return {
|
||||
"compiled_sql": compiled_sql,
|
||||
"raw_response": response,
|
||||
"response_diagnostics": response_diagnostics,
|
||||
}
|
||||
|
||||
for item in result_payload:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
compiled_sql = str(item.get("query") or "").strip()
|
||||
top_level_candidates: List[Tuple[str, Any]] = [
|
||||
("query", response.get("query")),
|
||||
("sql", response.get("sql")),
|
||||
("compiled_sql", response.get("compiled_sql")),
|
||||
]
|
||||
if isinstance(result_payload, dict):
|
||||
top_level_candidates.extend(
|
||||
[
|
||||
("result.query", result_payload.get("query")),
|
||||
("result.sql", result_payload.get("sql")),
|
||||
("result.compiled_sql", result_payload.get("compiled_sql")),
|
||||
]
|
||||
)
|
||||
|
||||
for source, candidate in top_level_candidates:
|
||||
compiled_sql = str(candidate or "").strip()
|
||||
response_diagnostics.append(
|
||||
{
|
||||
"source": source,
|
||||
"has_query": bool(compiled_sql),
|
||||
}
|
||||
)
|
||||
if compiled_sql:
|
||||
return {
|
||||
"compiled_sql": compiled_sql,
|
||||
"raw_response": response,
|
||||
"response_diagnostics": response_diagnostics,
|
||||
}
|
||||
|
||||
raise SupersetAPIError("Superset chart/data response did not expose compiled SQL in result[].query")
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient._extract_compiled_sql_from_chart_data_response:Function]
|
||||
raise SupersetAPIError(
|
||||
"Superset preview response did not expose compiled SQL "
|
||||
f"(diagnostics={response_diagnostics!r})"
|
||||
)
|
||||
# [/DEF:backend.src.core.superset_client.SupersetClient._extract_compiled_sql_from_preview_response:Function]
|
||||
|
||||
# [DEF:SupersetClient.update_dataset:Function]
|
||||
# @COMPLEXITY: 3
|
||||
|
||||
Reference in New Issue
Block a user