[docs]classAnalyticalQuery:""" This AnalyticalQuery class provides methods to execute SQL queries on Data Solutions analytical tables. It supports fetching results as JSON or a pandas DataFrame, and provides query execution statistics. """def__init__(self,api_key:str):""" Initialize the Analytical class with the provided API key. :param api_key: The API key for accessing the analytical service. :type api_key: str """self.api_key=api_key
[docs]def__call__(self,query:str,parameters:dict[str]={},polling_interval_sec:int=5,autopaginate:bool=True,)->"AnalyticalQuery":""" Execute a SQL query asynchronously using the provided parameters and polling interval. Autopagination is enabled by default. If autopagination is enabled, the method will fetch all pages of results and return a single AnalyticalQuery object with all results. Otherwise, the method will return an AnalyticalQuery object with the first page of results. Use the next_page() method to fetch the next page of results and the stats() method to get statistics that can help you determine the approach to take to fetch all results. :param query: The SQL query to be executed. :type query: str :param parameters: A dictionary of parameters to be used in the query. :type parameters: dict[str], optional :param polling_interval_sec: The interval in seconds between status checks. The minimum value is 5 seconds. :type polling_interval_sec: int, optional :param autopaginate: Whether to automatically retrieve full results instead of individual pages. :type autopaginate: bool, optional :return: An instance of the Analytical class with query results. :rtype: Analytical :raises DataSolutionsAPIException: Raises an exception if the query ID is not returned. :raises DataSolutionsSDKException: Raises an exception if an error occurs during query execution. :raises Exception: Raises an exception if an unexpected error occurs. """self._status_code=0self.results=[]self._stats={}self.json_response={}self.dataframe_data=Noneself._status="error"self.error_message=""self.error_details=""self.next_url=Noneself._total_pages=0self.query_id=Noneself.exception=UnhandledException()ifpolling_interval_sec<5:polling_interval_sec=5query_execution_url=(f"{BASE_URL['base_url']}/{ANALYTICAL_ENDPOINTS['async_query_execution']}")body={"sql":query,"parameters":parameters,}async_response=issue_request(api_key=self.api_key,url=query_execution_url,body=body,method="POST",)self.query_id=async_response.get("query_id")ifnotself.query_id:raiseDataSolutionsAPIException("Unexpected response. Query ID was not returned.")async_query_status_url=f"{BASE_URL['base_url']}/{ANALYTICAL_ENDPOINTS['async_query_status']}?query_id={self.query_id}"try:# The query will execute asynchronously. We need to keep polling the status endpoint to check if the query has completed.whileTrue:self.json_response=issue_request(api_key=self.api_key,url=async_query_status_url,method="GET",)self._status=self.json_response["status"]ifself._status=="running"orself._status=="pending":print(f"Query is still {self._status}. Checking status again in {polling_interval_sec} seconds.")sleep(polling_interval_sec)elifself._status=="error":self.error_message=self.json_response["message"]self.error_details=self.json_response.get("details")breakelifself._status=="success":self._status_code=200self._stats=self.json_response["stats"]self.results=self.json_response["results"]self.next_url=self.json_response["next"]self._total_pages=self._stats["total_pages"]breakifautopaginate:# We make a new list to avoid modifying the original results listresults=self.results.copy()current_page=self._stats["last_processed_page_index"]+1total_pages=self._total_pages# tqdm displays a progress bar while fetching datawithtqdm(total=total_pages,desc="Fetching data",unit="page")aspbar:whileself.has_next():_next_page=self.next_page()if_next_page._status!="error":next_page_results=_next_page.resultsresults.extend(next_page_results)# Keep appending results to the listpbar.update(1)# Update the progress barcurrent_page+=1else:raise_next_page.exceptionpbar.update(1)# Update the progress bar to 100%self.results=resultsself.next_url=NoneexceptDataSolutionsSDKExceptionase:self._status="error"self.exception=e.get_exception()self._status_code=e.status_codeexceptExceptionase:self._status="error"self.exception=UnhandledException(details=e,)returnself
[docs]defnext_page(self)->"Analytical":""" Fetch the next page of analytical query results. :return: An instance of the Analytical class with the next page of results. :rtype: Analytical :raises BadRequest: Raises an exception if there is no next page available. """ifself.next_url:self.json_response=issue_request(api_key=self.api_key,url=self.next_url,method="GET",)self._status=self.json_response["status"]ifself._status=="error":self.error_message=self.json_response["message"]self.error_details=self.json_response.get("details")elifself._status=="success":self._status=self._statusself._stats=self.json_response["stats"]self.results=self.json_response["results"]self.next_url=self.json_response["next"]else:raiseBadRequest("No next page available. Use the method has_next() to check if there is a next page that can be retrieved.")returnself
[docs]defjson(self)->dict:""" Return results as a JSON. :raises Exception: Raises an exception if the query resulted in an error. :return: Results of the SQL query as a JSON. :rtype: dict :raises Exception: Raises an exception if the query resulted in an error. """ifself._status!="error":returnself.resultselse:raiseself.exception
[docs]defdf(self)->pd.DataFrame:""" Convert query results into a pandas DataFrame. :raises Exception: Raises an exception if the query resulted in an error. :return: DataFrame containing the results of the SQL query. :rtype: pd.DataFrame :raises Exception: Raises an exception if the query resulted in an error. """ifself._status!="error":self.dataframe_data=pd.DataFrame(self.results)returnself.dataframe_dataelse:raiseself.exception
[docs]defstats(self)->dict:""" Get the statistics of the executed query. :return: Statistics of the query execution. :rtype: dict :raises Exception: Raises an exception if the query resulted in an error. """ifself._status!="error":returnself._statselse:raiseself.exception
[docs]defstatus_code(self)->int:""" Get the HTTP status code of the response. :return: HTTP status code. :rtype: int """returnself._status_code
[docs]defwas_successful(self)->bool:""" Determine if the query executed successfully. :return: True if the query was successful, False otherwise. :rtype: bool """ifself._status!="error":returnTruereturnFalse
[docs]deftotal_pages(self)->int:""" Return total number of pages. :return: Number of pages. :rtype: int """returnself._total_pages
[docs]defhas_next(self)->bool:""" Return if the next page exists. :return: Whether next page exists. :rtype: bool """ifself.next_url:returnTruereturnFalse