Skip to content

Commit 55d5f92

Browse files
authored
hashing query and returning full response from refresh (#26)
1 parent dfc0946 commit 55d5f92

File tree

6 files changed

+60
-26
lines changed

6 files changed

+60
-26
lines changed

dune_client/client.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
ResultsResponse,
2222
ExecutionState,
2323
)
24-
from dune_client.types import DuneRecord
24+
2525
from dune_client.query import Query
2626

2727
log = logging.getLogger(__name__)
@@ -112,7 +112,7 @@ def cancel_execution(self, job_id: str) -> bool:
112112
except KeyError as err:
113113
raise DuneError(response_json, "CancellationResponse", err) from err
114114

115-
def refresh(self, query: Query, ping_frequency: int = 5) -> list[DuneRecord]:
115+
def refresh(self, query: Query, ping_frequency: int = 5) -> ResultsResponse:
116116
"""
117117
Executes a Dune `query`, waits until execution completes,
118118
fetches and returns the results.
@@ -125,16 +125,8 @@ def refresh(self, query: Query, ping_frequency: int = 5) -> list[DuneRecord]:
125125
time.sleep(ping_frequency)
126126
status = self.get_status(job_id)
127127

128-
if status.state == ExecutionState.COMPLETED:
129-
full_response = self.get_result(job_id)
130-
assert (
131-
full_response.result is not None
132-
), f"Expected Results on completed execution status {full_response}"
133-
return full_response.result.rows
134-
135-
if status.state == ExecutionState.CANCELLED:
136-
log.info("Execution Cancelled, returning empty record set")
137-
return []
138-
139-
log.error(status)
140-
raise Exception(f"{status}. Perhaps your query took too long to run!")
128+
full_response = self.get_result(job_id)
129+
if status.state == ExecutionState.FAILED:
130+
log.error(status)
131+
raise Exception(f"{status}. Perhaps your query took too long to run!")
132+
return full_response

dune_client/interface.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
Abstract class for a basic Dune Interface with refresh method used by Query Runner.
33
"""
44
from abc import ABC
5-
from typing import List
65

6+
from dune_client.models import ResultsResponse
77
from dune_client.query import Query
8-
from dune_client.types import DuneRecord
98

109

1110
# pylint: disable=too-few-public-methods
@@ -14,7 +13,7 @@ class DuneInterface(ABC):
1413
User Facing Methods for a Dune Client
1514
"""
1615

17-
def refresh(self, query: Query) -> List[DuneRecord]:
16+
def refresh(self, query: Query) -> ResultsResponse:
1817
"""
1918
Executes a Dune query, waits till query execution completes,
2019
fetches and returns the results.

dune_client/models.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,25 @@ def from_dict(cls, data: dict[str, str | int | ResultData]) -> ResultsResponse:
208208
assert isinstance(data["execution_id"], str)
209209
assert isinstance(data["query_id"], int)
210210
assert isinstance(data["state"], str)
211-
assert isinstance(data["result"], dict)
211+
result = data.get("result", {})
212+
assert isinstance(result, dict)
212213
return cls(
213214
execution_id=data["execution_id"],
214215
query_id=int(data["query_id"]),
215216
state=ExecutionState(data["state"]),
216217
times=TimeData.from_dict(data),
217-
result=ExecutionResult.from_dict(data["result"]),
218+
result=ExecutionResult.from_dict(result) if result else None,
218219
)
220+
221+
def get_rows(self) -> list[DuneRecord]:
222+
"""
223+
Absorbs the Optional check and returns the result rows.
224+
When execution is a non-complete terminal state, returns empty list.
225+
"""
226+
227+
if self.state == ExecutionState.COMPLETED:
228+
assert self.result is not None, f"No Results on completed execution {self}"
229+
return self.result.rows
230+
231+
log.info(f"execution {self.state} returning empty list")
232+
return []

dune_client/query.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
class Query:
1313
"""Basic data structure constituting a Dune Analytics Query."""
1414

15-
name: str
1615
query_id: int
16+
name: Optional[str] = "unnamed"
1717
params: Optional[List[QueryParameter]] = None
1818

1919
def base_url(self) -> str:
@@ -33,3 +33,10 @@ def url(self) -> str:
3333
[self.base_url(), urllib.parse.quote_plus(params, safe="=&?")]
3434
)
3535
return self.base_url()
36+
37+
def __hash__(self) -> int:
38+
"""
39+
This contains the query ID and the values of relevant parameters.
40+
Thus, it is unique for caching purposes
41+
"""
42+
return self.url().__hash__()

tests/e2e/test_client.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ def test_get_status(self):
3636
dune = DuneClient(self.valid_api_key)
3737
job_id = dune.execute(query).execution_id
3838
status = dune.get_status(job_id)
39-
self.assertEqual(status.state, ExecutionState.EXECUTING)
39+
self.assertTrue(
40+
status.state in [ExecutionState.EXECUTING, ExecutionState.PENDING]
41+
)
4042

4143
def test_refresh(self):
4244
dune = DuneClient(self.valid_api_key)
43-
results = dune.refresh(self.query)
45+
results = dune.refresh(self.query).get_rows()
4446
self.assertGreater(len(results), 0)
4547

4648
def test_parameters_recognized(self):
@@ -58,7 +60,7 @@ def test_parameters_recognized(self):
5860
dune = DuneClient(self.valid_api_key)
5961
results = dune.refresh(query)
6062
self.assertEqual(
61-
results,
63+
results.get_rows(),
6264
[
6365
{
6466
"text_field": "different word",
@@ -88,10 +90,14 @@ def test_cancel_execution(self):
8890
query_id=1229120,
8991
)
9092
execution_response = dune.execute(query)
93+
job_id = execution_response.execution_id
9194
# POST Cancellation
92-
success = dune.cancel_execution(execution_response.execution_id)
95+
success = dune.cancel_execution(job_id)
9396
self.assertTrue(success)
9497

98+
results = dune.get_result(job_id)
99+
self.assertEqual(results.state, ExecutionState.CANCELLED)
100+
95101
def test_invalid_api_key_error(self):
96102
dune = DuneClient(api_key="Invalid Key")
97103
with self.assertRaises(DuneError) as err:

tests/unit/test_query.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,27 @@ def test_url(self):
2424
self.query.url(),
2525
"https://dune.com/queries/0?Enum=option1&Text=plain+text&Number=12&Date=2021-01-01+12%3A34%3A56",
2626
)
27-
self.assertEqual(Query("", 0, []).url(), "https://dune.com/queries/0")
27+
self.assertEqual(Query(0, "", []).url(), "https://dune.com/queries/0")
2828

2929
def test_parameters(self):
3030
self.assertEqual(self.query.parameters(), self.query_params)
3131

32+
def test_hash(self):
33+
# Same ID, different params
34+
query1 = Query(query_id=0, params=[QueryParameter.text_type("Text", "word1")])
35+
query2 = Query(query_id=0, params=[QueryParameter.text_type("Text", "word2")])
36+
self.assertNotEqual(hash(query1), hash(query2))
37+
38+
# Different ID, same
39+
query1 = Query(query_id=0)
40+
query2 = Query(query_id=1)
41+
self.assertNotEqual(hash(query1), hash(query2))
42+
43+
# Different ID different params
44+
query1 = Query(query_id=0)
45+
query2 = Query(query_id=1, params=[QueryParameter.number_type("num", 1)])
46+
self.assertNotEqual(hash(query1), hash(query2))
47+
3248

3349
if __name__ == "__main__":
3450
unittest.main()

0 commit comments

Comments
 (0)