77
88import time
99from io import BytesIO
10- from typing import Any
10+ from typing import Any , Optional , Union
1111
1212import requests
1313from requests import Response , JSONDecodeError
@@ -50,13 +50,14 @@ def _handle_response(
5050 def _route_url (self , route : str ) -> str :
5151 return f"{ self .BASE_URL } { self .API_PATH } /{ route } "
5252
53- def _get (self , route : str ) -> Any :
53+ def _get (self , route : str , params : Optional [ Any ] = None ) -> Any :
5454 url = self ._route_url (route )
5555 self .logger .debug (f"GET received input url={ url } " )
5656 response = requests .get (
5757 url ,
5858 headers = {"x-dune-api-key" : self .token },
5959 timeout = self .DEFAULT_TIMEOUT ,
60+ params = params ,
6061 )
6162 return self ._handle_response (response )
6263
@@ -71,14 +72,20 @@ def _post(self, route: str, params: Any) -> Any:
7172 )
7273 return self ._handle_response (response )
7374
74- def execute (self , query : Query ) -> ExecutionResponse :
75+ def execute (
76+ self , query : Query , performance : Optional [str ] = None
77+ ) -> ExecutionResponse :
7578 """Post's to Dune API for execute `query`"""
79+ self .logger .info (
80+ f"executing { query .query_id } on { performance or self .performance } cluster"
81+ )
7682 response_json = self ._post (
7783 route = f"query/{ query .query_id } /execute" ,
7884 params = {
7985 "query_parameters" : {
8086 p .key : p .to_dict ()["value" ] for p in query .parameters ()
81- }
87+ },
88+ "performance" : performance or self .performance ,
8289 },
8390 )
8491 try :
@@ -122,6 +129,32 @@ def get_result_csv(self, job_id: str) -> ExecutionResultCSV:
122129 response .raise_for_status ()
123130 return ExecutionResultCSV (data = BytesIO (response .content ))
124131
132+ def get_latest_result (self , query : Union [Query , str , int ]) -> ResultsResponse :
133+ """
134+ GET the latest results for a query_id without having to execute the query again.
135+
136+ :param query: :class:`Query` object OR query id as string | int
137+
138+ https://dune.com/docs/api/api-reference/latest_results/
139+ """
140+ if isinstance (query , Query ):
141+ params = {
142+ f"params.{ p .key } " : p .to_dict ()["value" ] for p in query .parameters ()
143+ }
144+ query_id = query .query_id
145+ else :
146+ params = None
147+ query_id = query
148+
149+ response_json = self ._get (
150+ route = f"query/{ query_id } /results" ,
151+ params = params ,
152+ )
153+ try :
154+ return ResultsResponse .from_dict (response_json )
155+ except KeyError as err :
156+ raise DuneError (response_json , "ResultsResponse" , err ) from err
157+
125158 def cancel_execution (self , job_id : str ) -> bool :
126159 """POST Execution Cancellation to Dune API for `job_id` (aka `execution_id`)"""
127160 response_json = self ._post (route = f"execution/{ job_id } /cancel" , params = None )
@@ -132,8 +165,13 @@ def cancel_execution(self, job_id: str) -> bool:
132165 except KeyError as err :
133166 raise DuneError (response_json , "CancellationResponse" , err ) from err
134167
135- def _refresh (self , query : Query , ping_frequency : int = 5 ) -> str :
136- job_id = self .execute (query ).execution_id
168+ def _refresh (
169+ self ,
170+ query : Query ,
171+ ping_frequency : int = 5 ,
172+ performance : Optional [str ] = None ,
173+ ) -> str :
174+ job_id = self .execute (query = query , performance = performance ).execution_id
137175 status = self .get_status (job_id )
138176 while status .state not in ExecutionState .terminal_states ():
139177 self .logger .info (
@@ -147,25 +185,45 @@ def _refresh(self, query: Query, ping_frequency: int = 5) -> str:
147185
148186 return job_id
149187
150- def refresh (self , query : Query , ping_frequency : int = 5 ) -> ResultsResponse :
188+ def refresh (
189+ self ,
190+ query : Query ,
191+ ping_frequency : int = 5 ,
192+ performance : Optional [str ] = None ,
193+ ) -> ResultsResponse :
151194 """
152195 Executes a Dune `query`, waits until execution completes,
153196 fetches and returns the results.
154197 Sleeps `ping_frequency` seconds between each status request.
155198 """
156- job_id = self ._refresh (query , ping_frequency = ping_frequency )
199+ job_id = self ._refresh (
200+ query ,
201+ ping_frequency = ping_frequency ,
202+ performance = performance ,
203+ )
157204 return self .get_result (job_id )
158205
159- def refresh_csv (self , query : Query , ping_frequency : int = 5 ) -> ExecutionResultCSV :
206+ def refresh_csv (
207+ self ,
208+ query : Query ,
209+ ping_frequency : int = 5 ,
210+ performance : Optional [str ] = None ,
211+ ) -> ExecutionResultCSV :
160212 """
161213 Executes a Dune query, waits till execution completes,
162214 fetches and the results in CSV format
163215 (use it load the data directly in pandas.from_csv() or similar frameworks)
164216 """
165- job_id = self ._refresh (query , ping_frequency = ping_frequency )
217+ job_id = self ._refresh (
218+ query ,
219+ ping_frequency = ping_frequency ,
220+ performance = performance ,
221+ )
166222 return self .get_result_csv (job_id )
167223
168- def refresh_into_dataframe (self , query : Query ) -> Any :
224+ def refresh_into_dataframe (
225+ self , query : Query , performance : Optional [str ] = None
226+ ) -> Any :
169227 """
170228 Execute a Dune Query, waits till execution completes,
171229 fetched and returns the result as a Pandas DataFrame
@@ -178,5 +236,5 @@ def refresh_into_dataframe(self, query: Query) -> Any:
178236 raise ImportError (
179237 "dependency failure, pandas is required but missing"
180238 ) from exc
181- data = self .refresh_csv (query ).data
239+ data = self .refresh_csv (query , performance = performance ).data
182240 return pandas .read_csv (data )
0 commit comments