Skip to content

Commit

Permalink
feat: implement resource actions clients (#252)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jooola authored Aug 14, 2023
1 parent a4df4fa commit 4bb9a97
Show file tree
Hide file tree
Showing 22 changed files with 727 additions and 26 deletions.
3 changes: 3 additions & 0 deletions docs/api.clients.actions.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
ActionsClient
==================

.. autoclass:: hcloud.actions.client.ResourceActionsClient
:members:

.. autoclass:: hcloud.actions.client.ActionsClient
:members:
:inherited-members:

.. autoclass:: hcloud.actions.client.BoundAction
:members:
Expand Down
7 changes: 6 additions & 1 deletion hcloud/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from __future__ import annotations

from .client import ActionsClient, ActionsPageResult, BoundAction # noqa: F401
from .client import ( # noqa: F401
ActionsClient,
ActionsPageResult,
BoundAction,
ResourceActionsClient,
)
from .domain import ( # noqa: F401
Action,
ActionException,
Expand Down
79 changes: 70 additions & 9 deletions hcloud/actions/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import time
import warnings
from typing import TYPE_CHECKING, Any, NamedTuple

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


class ActionsClient(ClientEntityBase):
_client: Client
class ResourceActionsClient(ClientEntityBase):
_resource: str

def __init__(self, client: Client, resource: str | None):
super().__init__(client)
self._resource = resource or ""

def get_by_id(self, id: int) -> BoundAction:
"""Get a specific action by its ID.
:param id: int
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
"""

response = self._client.request(url=f"/actions/{id}", method="GET")
return BoundAction(self, response["action"])
response = self._client.request(
url=f"{self._resource}/actions/{id}",
method="GET",
)
return BoundAction(self._client.actions, response["action"])

def get_list(
self,
Expand All @@ -60,7 +67,7 @@ def get_list(
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Get a list of actions from this account
"""Get a list of actions.
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
Expand All @@ -82,9 +89,14 @@ def get_list(
if per_page is not None:
params["per_page"] = per_page

response = self._client.request(url="/actions", method="GET", params=params)
response = self._client.request(
url=f"{self._resource}/actions",
method="GET",
params=params,
)
actions = [
BoundAction(self, action_data) for action_data in response["actions"]
BoundAction(self._client.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))

Expand All @@ -93,7 +105,7 @@ def get_all(
status: list[str] | None = None,
sort: list[str] | None = None,
) -> list[BoundAction]:
"""Get all actions of the account
"""Get all actions.
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
Expand All @@ -102,3 +114,52 @@ def get_all(
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
"""
return self._iter_pages(self.get_list, status=status, sort=sort)


class ActionsClient(ResourceActionsClient):
def __init__(self, client: Client):
super().__init__(client, None)

def get_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""
.. deprecated:: 1.28
Use :func:`client.<resource>.actions.get_list` instead,
e.g. using :attr:`hcloud.certificates.client.CertificatesClient.actions`.
`Starting 1 October 2023, it will no longer be available. <https://docs.hetzner.cloud/changelog#2023-07-20-actions-list-endpoint-is-deprecated>`_
"""
warnings.warn(
"The 'client.actions.get_list' method is deprecated, please use the "
"'client.<resource>.actions.get_list' method instead (e.g. "
"'client.certificates.actions.get_list').",
DeprecationWarning,
stacklevel=2,
)
return super().get_list(status=status, sort=sort, page=page, per_page=per_page)

def get_all(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
) -> list[BoundAction]:
"""
.. deprecated:: 1.28
Use :func:`client.<resource>.actions.get_all` instead,
e.g. using :attr:`hcloud.certificates.client.CertificatesClient.actions`.
`Starting 1 October 2023, it will no longer be available. <https://docs.hetzner.cloud/changelog#2023-07-20-actions-list-endpoint-is-deprecated>`_
"""
warnings.warn(
"The 'client.actions.get_all' method is deprecated, please use the "
"'client.<resource>.actions.get_all' method instead (e.g. "
"'client.certificates.actions.get_all').",
DeprecationWarning,
stacklevel=2,
)
return super().get_all(status=status, sort=sort)
12 changes: 11 additions & 1 deletion hcloud/certificates/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from .domain import (
Certificate,
Expand Down Expand Up @@ -106,6 +106,16 @@ class CertificatesPageResult(NamedTuple):
class CertificatesClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Certificates scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/certificates")

def get_by_id(self, id: int) -> BoundCertificate:
"""Get a specific certificate by its ID.
Expand Down
12 changes: 11 additions & 1 deletion hcloud/firewalls/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from .domain import (
CreateFirewallResponse,
Expand Down Expand Up @@ -161,6 +161,16 @@ class FirewallsPageResult(NamedTuple):
class FirewallsClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Firewalls scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/firewalls")

def get_actions_list(
self,
firewall: Firewall | BoundFirewall,
Expand Down
12 changes: 11 additions & 1 deletion hcloud/floating_ips/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from ..locations import BoundLocation
from .domain import CreateFloatingIPResponse, FloatingIP
Expand Down Expand Up @@ -141,6 +141,16 @@ class FloatingIPsPageResult(NamedTuple):
class FloatingIPsClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Floating IPs scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/floating_ips")

def get_actions_list(
self,
floating_ip: FloatingIP | BoundFloatingIP,
Expand Down
12 changes: 11 additions & 1 deletion hcloud/images/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from .domain import Image

Expand Down Expand Up @@ -113,6 +113,16 @@ class ImagesPageResult(NamedTuple):
class ImagesClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Images scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/images")

def get_actions_list(
self,
image: Image | BoundImage,
Expand Down
12 changes: 11 additions & 1 deletion hcloud/load_balancers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..certificates import BoundCertificate
from ..core import BoundModelBase, ClientEntityBase, Meta
from ..load_balancer_types import BoundLoadBalancerType
Expand Down Expand Up @@ -331,6 +331,16 @@ class LoadBalancersPageResult(NamedTuple):
class LoadBalancersClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Load Balancers scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/load_balancers")

def get_by_id(self, id: int) -> BoundLoadBalancer:
"""Get a specific Load Balancer
Expand Down
12 changes: 11 additions & 1 deletion hcloud/networks/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from .domain import Network, NetworkRoute, NetworkSubnet

Expand Down Expand Up @@ -168,6 +168,16 @@ class NetworksPageResult(NamedTuple):
class NetworksClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Networks scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/networks")

def get_by_id(self, id: int) -> BoundNetwork:
"""Get a specific network
Expand Down
12 changes: 11 additions & 1 deletion hcloud/primary_ips/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import BoundAction
from ..actions import BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from .domain import CreatePrimaryIPResponse, PrimaryIP

Expand Down Expand Up @@ -99,6 +99,16 @@ class PrimaryIPsPageResult(NamedTuple):
class PrimaryIPsClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Primary IPs scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/primary_ips")

def get_by_id(self, id: int) -> BoundPrimaryIP:
"""Returns a specific Primary IP object.
Expand Down
12 changes: 11 additions & 1 deletion hcloud/servers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from ..datacenters import BoundDatacenter
from ..firewalls import BoundFirewall
Expand Down Expand Up @@ -448,6 +448,16 @@ class ServersPageResult(NamedTuple):
class ServersClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Servers scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/servers")

def get_by_id(self, id: int) -> BoundServer:
"""Get a specific server
Expand Down
12 changes: 11 additions & 1 deletion hcloud/volumes/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING, Any, NamedTuple

from ..actions import ActionsPageResult, BoundAction
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
from ..core import BoundModelBase, ClientEntityBase, Meta
from ..locations import BoundLocation
from .domain import CreateVolumeResponse, Volume
Expand Down Expand Up @@ -137,6 +137,16 @@ class VolumesPageResult(NamedTuple):
class VolumesClient(ClientEntityBase):
_client: Client

actions: ResourceActionsClient
"""Volumes scoped actions client
:type: :class:`ResourceActionsClient <hcloud.actions.client.ResourceActionsClient>`
"""

def __init__(self, client: Client):
super().__init__(client)
self.actions = ResourceActionsClient(client, "/volumes")

def get_by_id(self, id: int) -> BoundVolume:
"""Get a specific volume by its id
Expand Down
Loading

0 comments on commit 4bb9a97

Please sign in to comment.