Skip to content

Commit 95a33a7

Browse files
authored
Add SQL execution and table listing features to Dune client (#184)
* Add SQL execution and table listing features to Dune client - Implemented `execute_sql` method in `ExecutionAPI` to allow direct SQL execution via the API. - Updated `ExtendedAPI` to utilize the new SQL execution method for running queries. - Added `list_tables` method in `TableAPI` to retrieve a paginated list of tables. - Introduced `UsageResponse`, `TableInfo`, and `ListTablesResponse` models for handling API responses. - Enhanced unit tests to cover new features and response parsing for usage and table information. * formating * fix how post params are done * test and fix * fixes to get_usage and proper tests
1 parent 90975e6 commit 95a33a7

File tree

7 files changed

+507
-36
lines changed

7 files changed

+507
-36
lines changed

dune_client/api/execution.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
get results: https://docs.dune.com/api-reference/executions/endpoint/get-execution-result
77
"""
88

9+
from __future__ import annotations
10+
911
from io import BytesIO
1012
from typing import Any
1113

@@ -47,6 +49,40 @@ def execute_query(self, query: QueryBase, performance: str | None = None) -> Exe
4749
except KeyError as err:
4850
raise DuneError(response_json, "ExecutionResponse", err) from err
4951

52+
def execute_sql(
53+
self,
54+
query_sql: str,
55+
performance: str | None = None,
56+
) -> ExecutionResponse:
57+
"""
58+
Execute arbitrary SQL directly via the API without creating a saved query.
59+
https://docs.dune.com/api-reference/executions/endpoint/execute-sql
60+
61+
Note: This endpoint does not support parameterized queries. If you need
62+
parameters, use the regular execute_query() with a saved query.
63+
64+
Args:
65+
query_sql: The SQL query string to execute
66+
performance: Optional performance tier ("medium" or "large")
67+
68+
Returns:
69+
ExecutionResponse with execution_id and state
70+
"""
71+
payload: dict[str, str] = {
72+
"sql": query_sql,
73+
"performance": performance or self.performance,
74+
}
75+
76+
self.logger.info(f"executing SQL on {performance or self.performance} cluster")
77+
response_json = self._post(
78+
route="/sql/execute",
79+
params=payload,
80+
)
81+
try:
82+
return ExecutionResponse.from_dict(response_json)
83+
except KeyError as err:
84+
raise DuneError(response_json, "ExecutionResponse", err) from err
85+
5086
def cancel_execution(self, job_id: str) -> bool:
5187
"""POST Execution Cancellation to Dune API for `job_id` (aka `execution_id`)"""
5288
response_json = self._post(

dune_client/api/extensions.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from dune_client.api.execution import ExecutionAPI
2020
from dune_client.api.query import QueryAPI
2121
from dune_client.api.table import TableAPI
22+
from dune_client.api.usage import UsageAPI
2223
from dune_client.models import (
2324
DuneError,
2425
ExecutionResultCSV,
@@ -38,7 +39,7 @@
3839
POLL_FREQUENCY_SECONDS = 1
3940

4041

41-
class ExtendedAPI(ExecutionAPI, QueryAPI, TableAPI, CustomEndpointAPI):
42+
class ExtendedAPI(ExecutionAPI, QueryAPI, TableAPI, UsageAPI, CustomEndpointAPI):
4243
"""
4344
Provides higher level helper methods for faster
4445
and easier development on top of the base ExecutionAPI.
@@ -321,20 +322,47 @@ def run_sql(
321322
name: str = "API Query",
322323
) -> ResultsResponse:
323324
"""
324-
Allows user to provide execute raw_sql via the CRUD interface
325-
- create, run, get results with optional archive/delete.
326-
- Query is by default made private and archived after execution.
325+
Execute arbitrary SQL directly via the API and return results.
326+
Uses the /sql/execute endpoint introduced in the Dune API.
327+
https://docs.dune.com/api-reference/executions/endpoint/execute-query
328+
329+
Note: The `name`, `is_private`, `archive_after`, and `params` parameters are
330+
kept for backward compatibility but are ignored when using the direct SQL execution
331+
endpoint. The /sql/execute endpoint does not support parameterized queries.
332+
333+
Args:
334+
query_sql: The SQL query string to execute
335+
params: (Ignored) Kept for backward compatibility
336+
is_private: (Ignored) Kept for backward compatibility
337+
archive_after: (Ignored) Kept for backward compatibility
338+
performance: Optional performance tier ("medium" or "large")
339+
ping_frequency: Seconds between status checks while polling
340+
name: (Ignored) Kept for backward compatibility
341+
342+
Returns:
343+
ResultsResponse with the query execution results
344+
327345
Requires Plus subscription!
328346
"""
329-
query = self.create_query(name, query_sql, params, is_private)
330-
try:
331-
results = self.run_query(
332-
query=query.base, performance=performance, ping_frequency=ping_frequency
333-
)
334-
finally:
335-
if archive_after:
336-
self.archive_query(query.base.query_id)
337-
return results
347+
# Execute SQL directly using the new endpoint
348+
job_id = self.execute_sql(
349+
query_sql=query_sql,
350+
performance=performance,
351+
).execution_id
352+
353+
# Poll for completion
354+
status = self.get_execution_status(job_id)
355+
while status.state not in ExecutionState.terminal_states():
356+
self.logger.info(f"waiting for query execution {job_id} to complete: {status}")
357+
time.sleep(ping_frequency)
358+
status = self.get_execution_status(job_id)
359+
360+
if status.state == ExecutionState.FAILED:
361+
self.logger.error(status)
362+
raise QueryFailedError(f"Error data: {status.error}")
363+
364+
# Fetch and return results
365+
return self._fetch_entire_result(self.get_execution_results(job_id))
338366

339367
######################
340368
# Deprecated Functions

dune_client/api/table.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
DeleteTableResult,
1515
DuneError,
1616
InsertTableResult,
17+
ListTablesResponse,
1718
)
1819

1920

@@ -137,3 +138,34 @@ def delete_table(self, namespace: str, table_name: str) -> DeleteTableResult:
137138
return DeleteTableResult.from_dict(response_json)
138139
except KeyError as err:
139140
raise DuneError(response_json, "DeleteTableResult", err) from err
141+
142+
def list_tables(
143+
self,
144+
limit: int | None = None,
145+
offset: int | None = None,
146+
) -> ListTablesResponse:
147+
"""
148+
https://docs.dune.com/api-reference/tables/endpoint/list
149+
Get a paginated list of tables uploaded to Dune.
150+
151+
Args:
152+
limit: Maximum number of tables to return (optional)
153+
offset: Offset for pagination (optional)
154+
155+
Returns:
156+
ListTablesResponse containing list of tables and pagination info
157+
"""
158+
params = {}
159+
if limit is not None:
160+
params["limit"] = limit
161+
if offset is not None:
162+
params["offset"] = offset
163+
164+
response_json = self._get(
165+
route="/tables",
166+
params=params if params else None,
167+
)
168+
try:
169+
return ListTablesResponse.from_dict(response_json)
170+
except KeyError as err:
171+
raise DuneError(response_json, "ListTablesResponse", err) from err

dune_client/api/usage.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
Usage API endpoints enable users to retrieve usage and billing information.
3+
https://docs.dune.com/api-reference/usage/endpoint/get-usage
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from dune_client.api.base import BaseRouter
9+
from dune_client.models import DuneError, UsageResponse
10+
11+
12+
class UsageAPI(BaseRouter):
13+
"""
14+
Implementation of Usage endpoints - Plus subscription only
15+
https://docs.dune.com/api-reference/usage/
16+
"""
17+
18+
def get_usage(
19+
self,
20+
start_date: str | None = None,
21+
end_date: str | None = None,
22+
) -> UsageResponse:
23+
"""
24+
Get credits usage data, billing periods, storage, etc.
25+
https://docs.dune.com/api-reference/usage/endpoint/get-usage
26+
27+
Args:
28+
start_date: Optional start date for the usage period (format: YYYY-MM-DD)
29+
end_date: Optional end date for the usage period (format: YYYY-MM-DD)
30+
31+
Returns:
32+
UsageResponse containing usage statistics and billing periods
33+
34+
Requires Plus subscription!
35+
"""
36+
payload = {}
37+
if start_date:
38+
payload["start_date"] = start_date
39+
if end_date:
40+
payload["end_date"] = end_date
41+
42+
response_json = self._post(
43+
route="/usage",
44+
params=payload,
45+
)
46+
try:
47+
return UsageResponse.from_dict(response_json)
48+
except KeyError as err:
49+
raise DuneError(response_json, "UsageResponse", err) from err

0 commit comments

Comments
 (0)