Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow using a custom poll_interval function #403

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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