diff --git a/hcloud/_client.py b/hcloud/_client.py index 9a1c2ee..84c4735 100644 --- a/hcloud/_client.py +++ b/hcloud/_client.py @@ -1,7 +1,7 @@ from __future__ import annotations import time -from typing import NoReturn +from typing import NoReturn, Protocol import requests @@ -26,6 +26,15 @@ from .volumes import VolumesClient +class PollIntervalFunction(Protocol): + def __call__(self, retries: int) -> float: + """ + Return a interval in seconds to wait between each API call. + + :param retries: Number of calls already made. + """ + + class Client: """Base Client for accessing the Hetzner Cloud API""" @@ -39,7 +48,8 @@ def __init__( api_endpoint: str = "https://api.hetzner.cloud/v1", application_name: str | None = None, application_version: str | None = None, - poll_interval: int = 1, + poll_interval: int | float | PollIntervalFunction = 1.0, + poll_max_retries: int = 120, timeout: float | tuple[float, float] | None = None, ): """Create a new Client instance @@ -48,7 +58,11 @@ def __init__( :param api_endpoint: Hetzner Cloud API endpoint :param application_name: Your application name :param application_version: Your application _version - :param poll_interval: Interval for polling information from Hetzner Cloud API in seconds + :param poll_interval: + Interval in seconds to use when polling actions from the API. + You may pass a function to compute a custom poll interval. + :param poll_max_retries: + Max retries before timeout when polling actions from the API. :param timeout: Requests timeout in seconds """ self.token = token @@ -57,7 +71,12 @@ def __init__( self._application_version = application_version self._requests_session = requests.Session() self._requests_timeout = timeout - self._poll_interval = poll_interval + + if isinstance(poll_interval, (int, float)): + self._poll_interval_func = lambda _: poll_interval # Constant poll interval + else: + self._poll_interval_func = poll_interval + self._poll_max_retries = poll_max_retries self.datacenters = DatacentersClient(self) """DatacentersClient Instance diff --git a/hcloud/actions/client.py b/hcloud/actions/client.py index 075d3d6..85fc007 100644 --- a/hcloud/actions/client.py +++ b/hcloud/actions/client.py @@ -16,20 +16,24 @@ class BoundAction(BoundModelBase, Action): model = Action - def wait_until_finished(self, max_retries: int = 100) -> None: - """Wait until the specific action has status="finished". + def wait_until_finished(self, max_retries: int | None = None) -> None: + """Wait until the specific action has status=finished. - :param max_retries: int - Specify how many retries will be performed before an ActionTimeoutException will be raised - :raises: ActionFailedException when action is finished with status=="error" - :raises: ActionTimeoutException when Action is still in "running" state after max_retries reloads. + :param max_retries: int Specify how many retries will be performed before an ActionTimeoutException will be raised. + :raises: ActionFailedException when action is finished with status==error + :raises: ActionTimeoutException when Action is still in status==running after max_retries is reached. """ + if max_retries is None: + # pylint: disable=protected-access + max_retries = self._client._client._poll_max_retries + + retries = 0 while self.status == Action.STATUS_RUNNING: - if max_retries > 0: + if retries < max_retries: self.reload() + retries += 1 # pylint: disable=protected-access - time.sleep(self._client._client._poll_interval) - max_retries = max_retries - 1 + time.sleep(self._client._client._poll_interval_func(retries)) else: raise ActionTimeoutException(action=self) diff --git a/tests/unit/actions/test_client.py b/tests/unit/actions/test_client.py index 6dd2c67..a8091b7 100644 --- a/tests/unit/actions/test_client.py +++ b/tests/unit/actions/test_client.py @@ -19,7 +19,8 @@ class TestBoundAction: def bound_running_action(self, mocked_requests): action_client = ActionsClient(client=mocked_requests) # Speed up tests that run `wait_until_finished` - action_client._client._poll_interval = 0.1 + action_client._client._poll_interval_func = lambda _: 0.0 + action_client._client._poll_max_retries = 3 return BoundAction( client=action_client,