diff --git a/dune_client/api/extensions.py b/dune_client/api/extensions.py index a127a08..87b93ae 100644 --- a/dune_client/api/extensions.py +++ b/dune_client/api/extensions.py @@ -2,7 +2,10 @@ Extended functionality for the ExecutionAPI """ from __future__ import annotations + +import logging import time + from io import BytesIO from typing import Union, Optional, Any @@ -19,6 +22,9 @@ ) from dune_client.query import QueryBase, parse_query_object_or_id from dune_client.types import QueryParameter +from dune_client.util import age_in_hours + +THREE_MONTHS_IN_HOURS = 2191 class ExtendedAPI(ExecutionAPI, QueryAPI): @@ -76,13 +82,17 @@ def run_query_dataframe( data = self.run_query_csv(query, ping_frequency, performance).data return pandas.read_csv(data) - def get_latest_result(self, query: Union[QueryBase, str, int]) -> ResultsResponse: + def get_latest_result( + self, + query: Union[QueryBase, str, int], + max_age_hours: int = THREE_MONTHS_IN_HOURS, + ) -> ResultsResponse: """ GET the latest results for a query_id without re-executing the query (doesn't use execution credits) :param query: :class:`Query` object OR query id as string or int - + :param max_age_hours: re-executes the query if result is older than max_age_hours https://dune.com/docs/api/api-reference/get-results/latest-results """ params, query_id = parse_query_object_or_id(query) @@ -91,7 +101,17 @@ def get_latest_result(self, query: Union[QueryBase, str, int]) -> ResultsRespons params=params, ) try: - return ResultsResponse.from_dict(response_json) + results = ResultsResponse.from_dict(response_json) + last_run = results.times.execution_ended_at + if last_run and age_in_hours(last_run) > max_age_hours: + # Query older than specified max age + logging.info( + f"results (from {last_run}) older than {max_age_hours} hours, re-running query" + ) + results = self.run_query( + query if isinstance(query, QueryBase) else QueryBase(query_id) + ) + return results except KeyError as err: raise DuneError(response_json, "ResultsResponse", err) from err diff --git a/dune_client/util.py b/dune_client/util.py index 743851f..50ac8cf 100644 --- a/dune_client/util.py +++ b/dune_client/util.py @@ -1,5 +1,5 @@ """Utility methods for package.""" -from datetime import datetime +from datetime import datetime, timezone from typing import Optional import pkg_resources @@ -20,3 +20,11 @@ def get_package_version(package_name: str) -> Optional[str]: return pkg_resources.get_distribution(package_name).version except pkg_resources.DistributionNotFound: return None + + +def age_in_hours(timestamp: datetime) -> float: + """ + Returns the time (in hours) between now and `timestamp` + """ + result_age = datetime.now(timezone.utc) - timestamp + return result_age.total_seconds() / (60 * 60) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 248898d..9b60f96 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,5 +1,6 @@ +import datetime import unittest -from dune_client.util import get_package_version +from dune_client.util import get_package_version, age_in_hours class TestUtils(unittest.TestCase): @@ -11,3 +12,9 @@ def test_package_version_some(self): def test_package_version_none(self): # Can't self refer (this should only work when user has dune-client installed). self.assertIsNone(get_package_version("unittest")) + + def test_age_in_hours(self): + march_ten_eighty_five = datetime.datetime( + 1985, 3, 10, tzinfo=datetime.timezone.utc + ) + self.assertGreaterEqual(age_in_hours(march_ten_eighty_five), 314159)