Skip to content

Commit 4bb9a97

Browse files
authored
feat: implement resource actions clients (#252)
This implements the new per resources actions endpoints. Related to https://docs.hetzner.cloud/changelog#2023-06-29-resource-action-endpoints ```py # Existing API client.actions.get_by_id() # /actions/{id} client.<resource>.get_actions_all() # /<resource>/{resource_id}/actions client.<resource>.get_actions_list() # /<resource>/{resource_id}/actions # New API client.<resource>.actions.get_all() # /<resource>/actions client.<resource>.actions.get_list() # /<resource>/actions client.<resource>.actions.get_by_id() # /<resource>/actions/{id} # Not planned client.<resource>.get_action_by_id() # /<resource>/{resource_id}/actions/{id} # Deprecated client.actions.get_all() # /actions client.actions.get_list() # /actions ``` One exception is the primary IPs client, it doesn't include calls to `/<resource>/{resource_id}/actions` or `/<resource>/{resource_id}/actions/{id}`: https://docs.hetzner.cloud/#primary-ip-actions * test: improve existing actions tests * feat: create ResourceActionsClient * feat: deprecated /actions endpoint * test: add tests for ResourceActionsClient * feat: spread ResourceActionsClient to all clients * test: add tests for all ResourceActionsClient * docs: improve reference docs * docs: add link to deprecation changelog
1 parent a4df4fa commit 4bb9a97

22 files changed

Lines changed: 727 additions & 26 deletions

File tree

docs/api.clients.actions.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
ActionsClient
22
==================
33

4+
.. autoclass:: hcloud.actions.client.ResourceActionsClient
5+
:members:
46

57
.. autoclass:: hcloud.actions.client.ActionsClient
68
:members:
9+
:inherited-members:
710

811
.. autoclass:: hcloud.actions.client.BoundAction
912
:members:

hcloud/actions/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from __future__ import annotations
22

3-
from .client import ActionsClient, ActionsPageResult, BoundAction # noqa: F401
3+
from .client import ( # noqa: F401
4+
ActionsClient,
5+
ActionsPageResult,
6+
BoundAction,
7+
ResourceActionsClient,
8+
)
49
from .domain import ( # noqa: F401
510
Action,
611
ActionException,

hcloud/actions/client.py

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import time
4+
import warnings
45
from typing import TYPE_CHECKING, Any, NamedTuple
56

67
from ..core import BoundModelBase, ClientEntityBase, Meta
@@ -40,18 +41,24 @@ class ActionsPageResult(NamedTuple):
4041
meta: Meta | None
4142

4243

43-
class ActionsClient(ClientEntityBase):
44-
_client: Client
44+
class ResourceActionsClient(ClientEntityBase):
45+
_resource: str
46+
47+
def __init__(self, client: Client, resource: str | None):
48+
super().__init__(client)
49+
self._resource = resource or ""
4550

4651
def get_by_id(self, id: int) -> BoundAction:
4752
"""Get a specific action by its ID.
4853
4954
:param id: int
5055
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
5156
"""
52-
53-
response = self._client.request(url=f"/actions/{id}", method="GET")
54-
return BoundAction(self, response["action"])
57+
response = self._client.request(
58+
url=f"{self._resource}/actions/{id}",
59+
method="GET",
60+
)
61+
return BoundAction(self._client.actions, response["action"])
5562

5663
def get_list(
5764
self,
@@ -60,7 +67,7 @@ def get_list(
6067
page: int | None = None,
6168
per_page: int | None = None,
6269
) -> ActionsPageResult:
63-
"""Get a list of actions from this account
70+
"""Get a list of actions.
6471
6572
:param status: List[str] (optional)
6673
Response will have only actions with specified statuses. Choices: `running` `success` `error`
@@ -82,9 +89,14 @@ def get_list(
8289
if per_page is not None:
8390
params["per_page"] = per_page
8491

85-
response = self._client.request(url="/actions", method="GET", params=params)
92+
response = self._client.request(
93+
url=f"{self._resource}/actions",
94+
method="GET",
95+
params=params,
96+
)
8697
actions = [
87-
BoundAction(self, action_data) for action_data in response["actions"]
98+
BoundAction(self._client.actions, action_data)
99+
for action_data in response["actions"]
88100
]
89101
return ActionsPageResult(actions, Meta.parse_meta(response))
90102

@@ -93,7 +105,7 @@ def get_all(
93105
status: list[str] | None = None,
94106
sort: list[str] | None = None,
95107
) -> list[BoundAction]:
96-
"""Get all actions of the account
108+
"""Get all actions.
97109
98110
:param status: List[str] (optional)
99111
Response will have only actions with specified statuses. Choices: `running` `success` `error`
@@ -102,3 +114,52 @@ def get_all(
102114
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
103115
"""
104116
return self._iter_pages(self.get_list, status=status, sort=sort)
117+
118+
119+
class ActionsClient(ResourceActionsClient):
120+
def __init__(self, client: Client):
121+
super().__init__(client, None)
122+
123+
def get_list(
124+
self,
125+
status: list[str] | None = None,
126+
sort: list[str] | None = None,
127+
page: int | None = None,
128+
per_page: int | None = None,
129+
) -> ActionsPageResult:
130+
"""
131+
.. deprecated:: 1.28
132+
Use :func:`client.<resource>.actions.get_list` instead,
133+
e.g. using :attr:`hcloud.certificates.client.CertificatesClient.actions`.
134+
135+
`Starting 1 October 2023, it will no longer be available. <https://docs.hetzner.cloud/changelog#2023-07-20-actions-list-endpoint-is-deprecated>`_
136+
"""
137+
warnings.warn(
138+
"The 'client.actions.get_list' method is deprecated, please use the "
139+
"'client.<resource>.actions.get_list' method instead (e.g. "
140+
"'client.certificates.actions.get_list').",
141+
DeprecationWarning,
142+
stacklevel=2,
143+
)
144+
return super().get_list(status=status, sort=sort, page=page, per_page=per_page)
145+
146+
def get_all(
147+
self,
148+
status: list[str] | None = None,
149+
sort: list[str] | None = None,
150+
) -> list[BoundAction]:
151+
"""
152+
.. deprecated:: 1.28
153+
Use :func:`client.<resource>.actions.get_all` instead,
154+
e.g. using :attr:`hcloud.certificates.client.CertificatesClient.actions`.
155+
156+
`Starting 1 October 2023, it will no longer be available. <https://docs.hetzner.cloud/changelog#2023-07-20-actions-list-endpoint-is-deprecated>`_
157+
"""
158+
warnings.warn(
159+
"The 'client.actions.get_all' method is deprecated, please use the "
160+
"'client.<resource>.actions.get_all' method instead (e.g. "
161+
"'client.certificates.actions.get_all').",
162+
DeprecationWarning,
163+
stacklevel=2,
164+
)
165+
return super().get_all(status=status, sort=sort)

hcloud/certificates/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import ActionsPageResult, BoundAction
5+
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
66
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import (
88
Certificate,
@@ -106,6 +106,16 @@ class CertificatesPageResult(NamedTuple):
106106
class CertificatesClient(ClientEntityBase):
107107
_client: Client
108108

109+
actions: ResourceActionsClient
110+
"""Certificates scoped actions client
111+
112+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
113+
"""
114+
115+
def __init__(self, client: Client):
116+
super().__init__(client)
117+
self.actions = ResourceActionsClient(client, "/certificates")
118+
109119
def get_by_id(self, id: int) -> BoundCertificate:
110120
"""Get a specific certificate by its ID.
111121

hcloud/firewalls/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import ActionsPageResult, BoundAction
5+
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
66
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import (
88
CreateFirewallResponse,
@@ -161,6 +161,16 @@ class FirewallsPageResult(NamedTuple):
161161
class FirewallsClient(ClientEntityBase):
162162
_client: Client
163163

164+
actions: ResourceActionsClient
165+
"""Firewalls scoped actions client
166+
167+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
168+
"""
169+
170+
def __init__(self, client: Client):
171+
super().__init__(client)
172+
self.actions = ResourceActionsClient(client, "/firewalls")
173+
164174
def get_actions_list(
165175
self,
166176
firewall: Firewall | BoundFirewall,

hcloud/floating_ips/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import ActionsPageResult, BoundAction
5+
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
66
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from ..locations import BoundLocation
88
from .domain import CreateFloatingIPResponse, FloatingIP
@@ -141,6 +141,16 @@ class FloatingIPsPageResult(NamedTuple):
141141
class FloatingIPsClient(ClientEntityBase):
142142
_client: Client
143143

144+
actions: ResourceActionsClient
145+
"""Floating IPs scoped actions client
146+
147+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
148+
"""
149+
150+
def __init__(self, client: Client):
151+
super().__init__(client)
152+
self.actions = ResourceActionsClient(client, "/floating_ips")
153+
144154
def get_actions_list(
145155
self,
146156
floating_ip: FloatingIP | BoundFloatingIP,

hcloud/images/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import ActionsPageResult, BoundAction
5+
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
66
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import Image
88

@@ -113,6 +113,16 @@ class ImagesPageResult(NamedTuple):
113113
class ImagesClient(ClientEntityBase):
114114
_client: Client
115115

116+
actions: ResourceActionsClient
117+
"""Images scoped actions client
118+
119+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
120+
"""
121+
122+
def __init__(self, client: Client):
123+
super().__init__(client)
124+
self.actions = ResourceActionsClient(client, "/images")
125+
116126
def get_actions_list(
117127
self,
118128
image: Image | BoundImage,

hcloud/load_balancers/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import ActionsPageResult, BoundAction
5+
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
66
from ..certificates import BoundCertificate
77
from ..core import BoundModelBase, ClientEntityBase, Meta
88
from ..load_balancer_types import BoundLoadBalancerType
@@ -331,6 +331,16 @@ class LoadBalancersPageResult(NamedTuple):
331331
class LoadBalancersClient(ClientEntityBase):
332332
_client: Client
333333

334+
actions: ResourceActionsClient
335+
"""Load Balancers scoped actions client
336+
337+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
338+
"""
339+
340+
def __init__(self, client: Client):
341+
super().__init__(client)
342+
self.actions = ResourceActionsClient(client, "/load_balancers")
343+
334344
def get_by_id(self, id: int) -> BoundLoadBalancer:
335345
"""Get a specific Load Balancer
336346

hcloud/networks/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import ActionsPageResult, BoundAction
5+
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
66
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import Network, NetworkRoute, NetworkSubnet
88

@@ -168,6 +168,16 @@ class NetworksPageResult(NamedTuple):
168168
class NetworksClient(ClientEntityBase):
169169
_client: Client
170170

171+
actions: ResourceActionsClient
172+
"""Networks scoped actions client
173+
174+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
175+
"""
176+
177+
def __init__(self, client: Client):
178+
super().__init__(client)
179+
self.actions = ResourceActionsClient(client, "/networks")
180+
171181
def get_by_id(self, id: int) -> BoundNetwork:
172182
"""Get a specific network
173183

hcloud/primary_ips/client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..actions import BoundAction
5+
from ..actions import BoundAction, ResourceActionsClient
66
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import CreatePrimaryIPResponse, PrimaryIP
88

@@ -99,6 +99,16 @@ class PrimaryIPsPageResult(NamedTuple):
9999
class PrimaryIPsClient(ClientEntityBase):
100100
_client: Client
101101

102+
actions: ResourceActionsClient
103+
"""Primary IPs scoped actions client
104+
105+
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
106+
"""
107+
108+
def __init__(self, client: Client):
109+
super().__init__(client)
110+
self.actions = ResourceActionsClient(client, "/primary_ips")
111+
102112
def get_by_id(self, id: int) -> BoundPrimaryIP:
103113
"""Returns a specific Primary IP object.
104114

0 commit comments

Comments
 (0)