Skip to content

Commit

Permalink
feat: allow using a custom poll_interval function (#403)
Browse files Browse the repository at this point in the history
Allow using a computed poll_interval sleeping time, (e.g. exponential
back off) when polling actions.
  • Loading branch information
jooola committed Jul 2, 2024
1 parent ebef774 commit 93eb56b
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 14 deletions.
27 changes: 23 additions & 4 deletions hcloud/_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import time
from typing import NoReturn
from typing import NoReturn, Protocol

import requests

Expand All @@ -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"""

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
22 changes: 13 additions & 9 deletions hcloud/actions/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion tests/unit/actions/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 93eb56b

Please sign in to comment.