From 8995dfa0618da1bafd854f92ef195fe5b47f694d Mon Sep 17 00:00:00 2001 From: Jan Kadlec Date: Wed, 8 Oct 2025 15:01:30 +0200 Subject: [PATCH] feat: add timeout attribute for `for_items()` JIRA: PSDK-223 risk: low --- .../gooddata_sdk/compute/model/execution.py | 17 ++++++++++++++--- gooddata-sdk/gooddata_sdk/compute/service.py | 16 +++++++++++++--- gooddata-sdk/gooddata_sdk/table.py | 18 +++++++++++++----- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/gooddata-sdk/gooddata_sdk/compute/model/execution.py b/gooddata-sdk/gooddata_sdk/compute/model/execution.py index 046d49226..7f2d9cf92 100644 --- a/gooddata-sdk/gooddata_sdk/compute/model/execution.py +++ b/gooddata-sdk/gooddata_sdk/compute/model/execution.py @@ -334,7 +334,12 @@ def dimensions(self) -> Any: def cancel_token(self) -> Optional[str]: return self._cancel_token - def read_result(self, limit: Union[int, list[int]], offset: Union[None, int, list[int]] = None) -> ExecutionResult: + def read_result( + self, + limit: Union[int, list[int]], + offset: Union[None, int, list[int]] = None, + timeout: Optional[Union[int, float, tuple]] = None, + ) -> ExecutionResult: """ Reads from the execution result. """ @@ -353,6 +358,7 @@ def read_result(self, limit: Union[int, list[int]], offset: Union[None, int, lis limit=_limit, _check_return_type=False, _return_http_data_only=False, + _request_timeout=timeout, **({"x_gdc_cancel_token": self.cancel_token} if self.cancel_token else {}), ) custom_headers = self._api_client.custom_headers @@ -450,8 +456,13 @@ def get_labels_and_formats(self) -> tuple[dict[str, str], dict[str, str]]: formats[m_group["localIdentifier"]] = m_group["format"] return labels, formats - def read_result(self, limit: Union[int, list[int]], offset: Union[None, int, list[int]] = None) -> ExecutionResult: - return self.bare_exec_response.read_result(limit, offset) + def read_result( + self, + limit: Union[int, list[int]], + offset: Union[None, int, list[int]] = None, + timeout: Optional[Union[int, float, tuple]] = None, + ) -> ExecutionResult: + return self.bare_exec_response.read_result(limit, offset, timeout) def cancel(self) -> None: """ diff --git a/gooddata-sdk/gooddata_sdk/compute/service.py b/gooddata-sdk/gooddata_sdk/compute/service.py index 64d2e31b3..cd9a5d522 100644 --- a/gooddata-sdk/gooddata_sdk/compute/service.py +++ b/gooddata-sdk/gooddata_sdk/compute/service.py @@ -4,7 +4,7 @@ import json import logging from collections.abc import Iterator -from typing import Any, Optional +from typing import Any, Optional, Union from gooddata_api_client import ApiException from gooddata_api_client.model.afm_cancel_tokens import AfmCancelTokens @@ -40,17 +40,27 @@ def __init__(self, api_client: GoodDataApiClient): self._actions_api = self._api_client.actions_api self._entities_api = self._api_client.entities_api - def for_exec_def(self, workspace_id: str, exec_def: ExecutionDefinition) -> Execution: + def for_exec_def( + self, + workspace_id: str, + exec_def: ExecutionDefinition, + timeout: Optional[Union[int, float, tuple]] = None, + ) -> Execution: """ Starts computation in GoodData.CN workspace, using the provided execution definition. Args: workspace_id: workspace identifier exec_def: execution definition - this prescribes what to calculate, how to place labels and metric values + timeout: request timeout in seconds. If a tuple is provided, it is used as (connection timeout, read timeout). into dimensions """ response, _, headers = self._actions_api.compute_report( - workspace_id, exec_def.as_api_model(), _check_return_type=False, _return_http_data_only=False + workspace_id, + exec_def.as_api_model(), + _check_return_type=False, + _return_http_data_only=False, + _request_timeout=timeout, ) return Execution( diff --git a/gooddata-sdk/gooddata_sdk/table.py b/gooddata-sdk/gooddata_sdk/table.py index 15251018b..10f297fa1 100644 --- a/gooddata-sdk/gooddata_sdk/table.py +++ b/gooddata-sdk/gooddata_sdk/table.py @@ -240,7 +240,11 @@ def _prepare_tabular_definition( return ExecutionDefinition(attributes=attributes, metrics=metrics, filters=filters, dimensions=dims) -def _as_table(response: ExecutionResponse, always_two_dimensional: bool = False) -> ExecutionTable: +def _as_table( + response: ExecutionResponse, + always_two_dimensional: bool = False, + timeout: Optional[Union[int, float, tuple]] = None, +) -> ExecutionTable: first_page_offset = [0, 0] first_page_limit = [_TABLE_ROW_BATCH_SIZE, _MAX_METRICS] @@ -256,7 +260,7 @@ def _as_table(response: ExecutionResponse, always_two_dimensional: bool = False) first_page_limit = [first_page_limit[0]] first_page_offset = [0] - first_page = response.read_result(offset=first_page_offset, limit=first_page_limit) + first_page = response.read_result(offset=first_page_offset, limit=first_page_limit, timeout=timeout) return ExecutionTable(response=response, first_page=first_page) @@ -793,7 +797,11 @@ def for_visualization( return _as_table(response, always_two_dimensional) def for_items( - self, workspace_id: str, items: list[Union[Attribute, Metric]], filters: Optional[list[Filter]] = None + self, + workspace_id: str, + items: list[Union[Attribute, Metric]], + filters: Optional[list[Filter]] = None, + timeout: Optional[Union[int, float, tuple]] = None, ) -> ExecutionTable: if filters is None: filters = [] @@ -810,6 +818,6 @@ def for_items( raise ValueError(f"Invalid input item: {item}. Expecting instance of Attribute or Metric") exec_def = _prepare_tabular_definition(attributes=attributes, metrics=metrics, filters=filters) - response = self._compute.for_exec_def(workspace_id=workspace_id, exec_def=exec_def) + response = self._compute.for_exec_def(workspace_id=workspace_id, exec_def=exec_def, timeout=timeout) - return _as_table(response) + return _as_table(response, timeout=timeout)