Skip to content

Commit 93eb56b

Browse files
authored
feat: allow using a custom poll_interval function (#403)
Allow using a computed poll_interval sleeping time, (e.g. exponential back off) when polling actions.
1 parent ebef774 commit 93eb56b

File tree

3 files changed

+38
-14
lines changed

3 files changed

+38
-14
lines changed

hcloud/_client.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import time
4-
from typing import NoReturn
4+
from typing import NoReturn, Protocol
55

66
import requests
77

@@ -26,6 +26,15 @@
2626
from .volumes import VolumesClient
2727

2828

29+
class PollIntervalFunction(Protocol):
30+
def __call__(self, retries: int) -> float:
31+
"""
32+
Return a interval in seconds to wait between each API call.
33+
34+
:param retries: Number of calls already made.
35+
"""
36+
37+
2938
class Client:
3039
"""Base Client for accessing the Hetzner Cloud API"""
3140

@@ -39,7 +48,8 @@ def __init__(
3948
api_endpoint: str = "https://api.hetzner.cloud/v1",
4049
application_name: str | None = None,
4150
application_version: str | None = None,
42-
poll_interval: int = 1,
51+
poll_interval: int | float | PollIntervalFunction = 1.0,
52+
poll_max_retries: int = 120,
4353
timeout: float | tuple[float, float] | None = None,
4454
):
4555
"""Create a new Client instance
@@ -48,7 +58,11 @@ def __init__(
4858
:param api_endpoint: Hetzner Cloud API endpoint
4959
:param application_name: Your application name
5060
:param application_version: Your application _version
51-
:param poll_interval: Interval for polling information from Hetzner Cloud API in seconds
61+
:param poll_interval:
62+
Interval in seconds to use when polling actions from the API.
63+
You may pass a function to compute a custom poll interval.
64+
:param poll_max_retries:
65+
Max retries before timeout when polling actions from the API.
5266
:param timeout: Requests timeout in seconds
5367
"""
5468
self.token = token
@@ -57,7 +71,12 @@ def __init__(
5771
self._application_version = application_version
5872
self._requests_session = requests.Session()
5973
self._requests_timeout = timeout
60-
self._poll_interval = poll_interval
74+
75+
if isinstance(poll_interval, (int, float)):
76+
self._poll_interval_func = lambda _: poll_interval # Constant poll interval
77+
else:
78+
self._poll_interval_func = poll_interval
79+
self._poll_max_retries = poll_max_retries
6180

6281
self.datacenters = DatacentersClient(self)
6382
"""DatacentersClient Instance

hcloud/actions/client.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,24 @@ class BoundAction(BoundModelBase, Action):
1616

1717
model = Action
1818

19-
def wait_until_finished(self, max_retries: int = 100) -> None:
20-
"""Wait until the specific action has status="finished".
19+
def wait_until_finished(self, max_retries: int | None = None) -> None:
20+
"""Wait until the specific action has status=finished.
2121
22-
:param max_retries: int
23-
Specify how many retries will be performed before an ActionTimeoutException will be raised
24-
:raises: ActionFailedException when action is finished with status=="error"
25-
:raises: ActionTimeoutException when Action is still in "running" state after max_retries reloads.
22+
:param max_retries: int Specify how many retries will be performed before an ActionTimeoutException will be raised.
23+
:raises: ActionFailedException when action is finished with status==error
24+
:raises: ActionTimeoutException when Action is still in status==running after max_retries is reached.
2625
"""
26+
if max_retries is None:
27+
# pylint: disable=protected-access
28+
max_retries = self._client._client._poll_max_retries
29+
30+
retries = 0
2731
while self.status == Action.STATUS_RUNNING:
28-
if max_retries > 0:
32+
if retries < max_retries:
2933
self.reload()
34+
retries += 1
3035
# pylint: disable=protected-access
31-
time.sleep(self._client._client._poll_interval)
32-
max_retries = max_retries - 1
36+
time.sleep(self._client._client._poll_interval_func(retries))
3337
else:
3438
raise ActionTimeoutException(action=self)
3539

tests/unit/actions/test_client.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class TestBoundAction:
1919
def bound_running_action(self, mocked_requests):
2020
action_client = ActionsClient(client=mocked_requests)
2121
# Speed up tests that run `wait_until_finished`
22-
action_client._client._poll_interval = 0.1
22+
action_client._client._poll_interval_func = lambda _: 0.0
23+
action_client._client._poll_max_retries = 3
2324

2425
return BoundAction(
2526
client=action_client,

0 commit comments

Comments
 (0)