1
0
Fork 0
mirror of https://github.com/ansible-collections/hetzner.hcloud.git synced 2026-02-04 08:01:49 +00:00

chore(deps): update dependency hcloud to v2.13.0 (#776)

This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [hcloud](https://redirect.github.com/hetznercloud/hcloud-python)
([changelog](https://redirect.github.com/hetznercloud/hcloud-python/blob/main/CHANGELOG.md))
| `2.12.0` -> `2.13.0` |
![age](https://developer.mend.io/api/mc/badges/age/pypi/hcloud/2.13.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/hcloud/2.12.0/2.13.0?slim=true)
|

---

### Release Notes

<details>
<summary>hetznercloud/hcloud-python (hcloud)</summary>

###
[`v2.13.0`](https://redirect.github.com/hetznercloud/hcloud-python/blob/HEAD/CHANGELOG.md#v2130)

[Compare
Source](https://redirect.github.com/hetznercloud/hcloud-python/compare/v2.12.0...v2.13.0)

##### Features

- add per primary ip actions list operations
([#&#8203;608](https://redirect.github.com/hetznercloud/hcloud-python/issues/608))
- deprecate datacenter in `primary ips` and `servers`
([#&#8203;609](https://redirect.github.com/hetznercloud/hcloud-python/issues/609))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/ansible-collections/hetzner.hcloud).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: jo <ljonas@riseup.net>
This commit is contained in:
renovate[bot] 2025-12-19 19:50:01 +01:00 committed by GitHub
parent f204b21ee0
commit af3e9f4bf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 1487 additions and 895 deletions

View file

@ -1,12 +1,18 @@
from __future__ import annotations from __future__ import annotations
from ._client import ( # noqa pylint: disable=C0414 from ._client import (
Client as Client, Client,
constant_backoff_function as constant_backoff_function, constant_backoff_function,
exponential_backoff_function as exponential_backoff_function, exponential_backoff_function,
) )
from ._exceptions import ( # noqa pylint: disable=C0414 from ._exceptions import APIException, HCloudException
APIException as APIException, from ._version import __version__
HCloudException as HCloudException,
) __all__ = [
from ._version import __version__ # noqa "__version__",
"Client",
"constant_backoff_function",
"exponential_backoff_function",
"APIException",
"HCloudException",
]

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import time import time
from http import HTTPStatus from http import HTTPStatus
from random import uniform from random import uniform
from typing import Protocol from typing import Any, Protocol
try: try:
import requests import requests
@ -75,7 +75,7 @@ def exponential_backoff_function(
""" """
def func(retries: int) -> float: def func(retries: int) -> float:
interval = base * multiplier**retries # Exponential backoff interval: float = base * multiplier**retries # Exponential backoff
interval = min(cap, interval) # Cap backoff interval = min(cap, interval) # Cap backoff
if jitter: if jitter:
interval = uniform(base, interval) # Add jitter interval = uniform(base, interval) # Add jitter
@ -295,7 +295,7 @@ class Client:
method: str, method: str,
url: str, url: str,
**kwargs, **kwargs,
) -> dict: ) -> dict[str, Any]:
"""Perform a request to the Hetzner Cloud API. """Perform a request to the Hetzner Cloud API.
:param method: Method to perform the request. :param method: Method to perform the request.
@ -348,7 +348,7 @@ class ClientBase:
method: str, method: str,
url: str, url: str,
**kwargs, **kwargs,
) -> dict: ) -> dict[str, Any]:
"""Perform a request to the provided URL. """Perform a request to the provided URL.
:param method: Method to perform the request. :param method: Method to perform the request.
@ -384,7 +384,7 @@ class ClientBase:
continue continue
raise raise
def _read_response(self, response) -> dict: def _read_response(self, response) -> dict[str, Any]:
correlation_id = response.headers.get("X-Correlation-Id") correlation_id = response.headers.get("X-Correlation-Id")
payload = {} payload = {}
try: try:
@ -407,7 +407,7 @@ class ClientBase:
correlation_id=correlation_id, correlation_id=correlation_id,
) )
error: dict = payload["error"] error: dict[str, Any] = payload["error"]
raise APIException( raise APIException(
code=error["code"], code=error["code"],
message=error["message"], message=error["message"],

View file

@ -1,3 +1,3 @@
from __future__ import annotations from __future__ import annotations
__version__ = "2.12.0" # x-releaser-pleaser-version __version__ = "2.13.0" # x-releaser-pleaser-version

View file

@ -2,24 +2,32 @@ from __future__ import annotations
from .client import ( from .client import (
ActionsClient, ActionsClient,
ActionSort,
ActionsPageResult, ActionsPageResult,
BoundAction, BoundAction,
ResourceActionsClient, ResourceActionsClient,
) )
from .domain import ( from .domain import (
Action, Action,
ActionError,
ActionException, ActionException,
ActionFailedException, ActionFailedException,
ActionResource,
ActionStatus,
ActionTimeoutException, ActionTimeoutException,
) )
__all__ = [ __all__ = [
"Action",
"ActionException",
"ActionFailedException",
"ActionTimeoutException",
"ActionsClient", "ActionsClient",
"ActionsPageResult", "ActionsPageResult",
"BoundAction", "BoundAction",
"ResourceActionsClient", "ResourceActionsClient",
"ActionSort",
"ActionStatus",
"Action",
"ActionResource",
"ActionError",
"ActionException",
"ActionFailedException",
"ActionTimeoutException",
] ]

View file

@ -2,16 +2,25 @@ from __future__ import annotations
import time import time
import warnings import warnings
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, Literal, NamedTuple
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import Action, ActionFailedException, ActionTimeoutException from .domain import Action, ActionFailedException, ActionStatus, ActionTimeoutException
if TYPE_CHECKING: if TYPE_CHECKING:
from .._client import Client from .._client import Client
class BoundAction(BoundModelBase, Action): __all__ = [
"ActionsClient",
"ActionsPageResult",
"BoundAction",
"ResourceActionsClient",
"ActionSort",
]
class BoundAction(BoundModelBase[Action], Action):
_client: ActionsClient _client: ActionsClient
model = Action model = Action
@ -45,12 +54,78 @@ class BoundAction(BoundModelBase, Action):
raise ActionFailedException(action=self) raise ActionFailedException(action=self)
ActionSort = Literal[
"id",
"id:asc",
"id:desc",
"command",
"command:asc",
"command:desc",
"status",
"status:asc",
"status:desc",
"started",
"started:asc",
"started:desc",
"finished",
"finished:asc",
"finished:desc",
]
class ActionsPageResult(NamedTuple): class ActionsPageResult(NamedTuple):
actions: list[BoundAction] actions: list[BoundAction]
meta: Meta meta: Meta
class ResourceActionsClient(ResourceClientBase): class ResourceClientBaseActionsMixin(ResourceClientBase):
def _get_action_by_id(
self,
base_url: str,
id: int,
) -> BoundAction:
response = self._client.request(
method="GET",
url=f"{base_url}/actions/{id}",
)
return BoundAction(
client=self._parent.actions,
data=response["action"],
)
def _get_actions_list(
self,
base_url: str,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
params: dict[str, Any] = {}
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request(
method="GET",
url=f"{base_url}/actions",
params=params,
)
return ActionsPageResult(
actions=[BoundAction(self._parent.actions, o) for o in response["actions"]],
meta=Meta.parse_meta(response),
)
class ResourceActionsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_resource: str _resource: str
def __init__(self, client: ResourceClientBase | Client, resource: str | None): def __init__(self, client: ResourceClientBase | Client, resource: str | None):
@ -66,69 +141,46 @@ class ResourceActionsClient(ResourceClientBase):
self._resource = resource or "" self._resource = resource or ""
def get_by_id(self, id: int) -> BoundAction: 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( Returns a specific Action by its ID.
url=f"{self._resource}/actions/{id}",
method="GET", :param id: ID of the Action.
) """
return BoundAction(self._parent.actions, response["action"]) return self._get_action_by_id(self._resource, id)
def get_list( def get_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Get a list of actions.
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions.
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request( :param status: Filter the Actions by status.
url=f"{self._resource}/actions", :param sort: Sort Actions by field and direction.
method="GET", :param page: Page number to get.
params=params, :param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
self._resource,
status=status,
sort=sort,
page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_all( def get_all(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Get all actions. """
Returns all Actions.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages(self.get_list, status=status, sort=sort) return self._iter_pages(self.get_list, status=status, sort=sort)
@ -139,8 +191,8 @@ class ActionsClient(ResourceActionsClient):
def get_list( def get_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
@ -162,8 +214,8 @@ class ActionsClient(ResourceActionsClient):
def get_all( def get_all(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
""" """
.. deprecated:: 1.28 .. deprecated:: 1.28

View file

@ -1,11 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any, Literal, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from .._exceptions import HCloudException from .._exceptions import HCloudException
from ..core import BaseDomain from ..core import BaseDomain
@ -13,6 +8,22 @@ from ..core import BaseDomain
if TYPE_CHECKING: if TYPE_CHECKING:
from .client import BoundAction from .client import BoundAction
__all__ = [
"ActionStatus",
"Action",
"ActionResource",
"ActionError",
"ActionException",
"ActionFailedException",
"ActionTimeoutException",
]
ActionStatus = Literal[
"running",
"success",
"error",
]
class Action(BaseDomain): class Action(BaseDomain):
"""Action Domain """Action Domain
@ -50,24 +61,35 @@ class Action(BaseDomain):
self, self,
id: int, id: int,
command: str | None = None, command: str | None = None,
status: str | None = None, status: ActionStatus | None = None,
progress: int | None = None, progress: int | None = None,
started: str | None = None, started: str | None = None,
finished: str | None = None, finished: str | None = None,
resources: list[dict] | None = None, resources: list[ActionResource] | None = None,
error: dict | None = None, error: ActionError | None = None,
): ):
self.id = id self.id = id
self.command = command self.command = command
self.status = status self.status = status
self.progress = progress self.progress = progress
self.started = isoparse(started) if started else None self.started = self._parse_datetime(started)
self.finished = isoparse(finished) if finished else None self.finished = self._parse_datetime(finished)
self.resources = resources self.resources = resources
self.error = error self.error = error
class ActionResource(TypedDict):
id: int
type: str
class ActionError(TypedDict):
code: str
message: str
details: dict[str, Any]
class ActionException(HCloudException): class ActionException(HCloudException):
"""A generic action exception""" """A generic action exception"""
@ -85,7 +107,8 @@ class ActionException(HCloudException):
extras.append(action.error["code"]) extras.append(action.error["code"])
else: else:
extras.append(action.command) if action.command is not None:
extras.append(action.command)
extras.append(str(action.id)) extras.append(str(action.id))
message += f" ({', '.join(extras)})" message += f" ({', '.join(extras)})"

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import ( from .domain import (
Certificate, Certificate,
@ -15,12 +22,24 @@ if TYPE_CHECKING:
from .._client import Client from .._client import Client
class BoundCertificate(BoundModelBase, Certificate): __all__ = [
"BoundCertificate",
"CertificatesPageResult",
"CertificatesClient",
]
class BoundCertificate(BoundModelBase[Certificate], Certificate):
_client: CertificatesClient _client: CertificatesClient
model = Certificate model = Certificate
def __init__(self, client: CertificatesClient, data: dict, complete: bool = True): def __init__(
self,
client: CertificatesClient,
data: dict[str, Any],
complete: bool = True,
):
status = data.get("status") status = data.get("status")
if status is not None: if status is not None:
error_data = status.get("error") error_data = status.get("error")
@ -36,22 +55,18 @@ class BoundCertificate(BoundModelBase, Certificate):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Certificate. """
Returns a paginated list of Actions for a Certificate.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -63,16 +78,14 @@ class BoundCertificate(BoundModelBase, Certificate):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Certificate. """
Returns all Actions for a Certificate.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions( return self._client.get_actions(
self, self,
@ -117,7 +130,10 @@ class CertificatesPageResult(NamedTuple):
meta: Meta meta: Meta
class CertificatesClient(ResourceClientBase): class CertificatesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/certificates" _base_url = "/certificates"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -306,59 +322,40 @@ class CertificatesClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
certificate: Certificate | BoundCertificate, certificate: Certificate | BoundCertificate,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Certificate.
:param certificate: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>` or :class:`Certificate <hcloud.certificates.domain.Certificate>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Certificate.
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request( :param certificate: Certificate to get the Actions for.
url=f"{self._base_url}/{certificate.id}/actions", :param status: Filter the Actions by status.
method="GET", :param sort: Sort Actions by field and direction.
params=params, :param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
f"{self._base_url}/{certificate.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
certificate: Certificate | BoundCertificate, certificate: Certificate | BoundCertificate,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Certificate. """
Returns all Actions for a Certificate.
:param certificate: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>` or :class:`Certificate <hcloud.certificates.domain.Certificate>` :param certificate: Certificate to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -2,11 +2,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING: if TYPE_CHECKING:
@ -14,6 +9,14 @@ if TYPE_CHECKING:
from .client import BoundCertificate from .client import BoundCertificate
__all__ = [
"Certificate",
"ManagedCertificateStatus",
"ManagedCertificateError",
"CreateManagedCertificateResponse",
]
class Certificate(BaseDomain, DomainIdentityMixin): class Certificate(BaseDomain, DomainIdentityMixin):
"""Certificate Domain """Certificate Domain
@ -72,9 +75,9 @@ class Certificate(BaseDomain, DomainIdentityMixin):
self.certificate = certificate self.certificate = certificate
self.domain_names = domain_names self.domain_names = domain_names
self.fingerprint = fingerprint self.fingerprint = fingerprint
self.not_valid_before = isoparse(not_valid_before) if not_valid_before else None self.not_valid_before = self._parse_datetime(not_valid_before)
self.not_valid_after = isoparse(not_valid_after) if not_valid_after else None self.not_valid_after = self._parse_datetime(not_valid_after)
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.labels = labels self.labels = labels
self.status = status self.status = status

View file

@ -2,11 +2,22 @@ from __future__ import annotations
import warnings import warnings
from collections.abc import Callable from collections.abc import Callable
from typing import TYPE_CHECKING, Any, ClassVar from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
from .domain import BaseDomain
if TYPE_CHECKING: if TYPE_CHECKING:
from .._client import Client, ClientBase from .._client import Client, ClientBase
from .domain import BaseDomain from .domain import Meta
__all__ = [
"ResourceClientBase",
"ClientEntityBase",
"BoundModelBase",
]
T = TypeVar("T")
class ResourceClientBase: class ResourceClientBase:
@ -23,10 +34,10 @@ class ResourceClientBase:
def _iter_pages( # type: ignore[no-untyped-def] def _iter_pages( # type: ignore[no-untyped-def]
self, self,
list_function: Callable, list_function: Callable[..., tuple[list[T], Meta]],
*args, *args,
**kwargs, **kwargs,
) -> list: ) -> list[T]:
results = [] results = []
page = 1 page = 1
@ -46,7 +57,12 @@ class ResourceClientBase:
return results return results
def _get_first_by(self, list_function: Callable, *args, **kwargs): # type: ignore[no-untyped-def] def _get_first_by( # type: ignore[no-untyped-def]
self,
list_function: Callable[..., tuple[list[T], Meta]],
*args,
**kwargs,
) -> T | None:
entities, _ = list_function(*args, **kwargs) entities, _ = list_function(*args, **kwargs)
return entities[0] if entities else None return entities[0] if entities else None
@ -69,15 +85,18 @@ class ClientEntityBase(ResourceClientBase):
super().__init__(client) super().__init__(client)
class BoundModelBase: Domain = TypeVar("Domain", bound=BaseDomain)
class BoundModelBase(Generic[Domain]):
"""Bound Model Base""" """Bound Model Base"""
model: type[BaseDomain] model: type[Domain]
def __init__( def __init__(
self, self,
client: ResourceClientBase, client: ResourceClientBase,
data: dict, data: dict[str, Any],
complete: bool = True, complete: bool = True,
): ):
""" """
@ -90,7 +109,7 @@ class BoundModelBase:
""" """
self._client = client self._client = client
self.complete = complete self.complete = complete
self.data_model = self.model.from_dict(data) self.data_model: Domain = self.model.from_dict(data)
def __getattr__(self, name: str): # type: ignore[no-untyped-def] def __getattr__(self, name: str): # type: ignore[no-untyped-def]
"""Allow magical access to the properties of the model """Allow magical access to the properties of the model
@ -103,9 +122,10 @@ class BoundModelBase:
value = getattr(self.data_model, name) value = getattr(self.data_model, name)
return value return value
def _get_self(self) -> BoundModelBase: def _get_self(self) -> BoundModelBase[Domain]:
assert hasattr(self._client, "get_by_id") assert hasattr(self._client, "get_by_id")
return self._client.get_by_id(self.data_model.id) assert hasattr(self.data_model, "id")
return self._client.get_by_id(self.data_model.id) # type: ignore
def reload(self) -> None: def reload(self) -> None:
"""Reloads the model and tries to get all data from the API""" """Reloads the model and tries to get all data from the API"""

View file

@ -1,13 +1,26 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from datetime import datetime
from typing import Any, overload
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
__all__ = [
"BaseDomain",
"DomainIdentityMixin",
"Pagination",
"Meta",
]
class BaseDomain: class BaseDomain:
__api_properties__: tuple __api_properties__: tuple[str, ...]
@classmethod @classmethod
def from_dict(cls, data: dict): # type: ignore[no-untyped-def] def from_dict(cls, data: dict[str, Any]): # type: ignore[no-untyped-def]
""" """
Build the domain object from the data dict. Build the domain object from the data dict.
""" """
@ -15,7 +28,7 @@ class BaseDomain:
return cls(**supported_data) return cls(**supported_data)
def __repr__(self) -> str: def __repr__(self) -> str:
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__] # type: ignore[var-annotated] kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__]
return f"{self.__class__.__qualname__}({', '.join(kwargs)})" return f"{self.__class__.__qualname__}({', '.join(kwargs)})"
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
@ -27,6 +40,16 @@ class BaseDomain:
return False return False
return True return True
@overload
def _parse_datetime(self, value: str) -> datetime: ...
@overload
def _parse_datetime(self, value: None) -> None: ...
def _parse_datetime(self, value: str | None) -> datetime | None:
if value is None:
return None
return isoparse(value)
class DomainIdentityMixin: class DomainIdentityMixin:
@ -106,7 +129,7 @@ class Meta(BaseDomain):
self.pagination = pagination self.pagination = pagination
@classmethod @classmethod
def parse_meta(cls, response: dict) -> Meta: def parse_meta(cls, response: dict[str, Any]) -> Meta:
""" """
If present, extract the meta details from the response and return a meta object. If present, extract the meta details from the response and return a meta object.
""" """

View file

@ -7,13 +7,19 @@ from ..locations import BoundLocation
from ..server_types import BoundServerType from ..server_types import BoundServerType
from .domain import Datacenter, DatacenterServerTypes from .domain import Datacenter, DatacenterServerTypes
__all__ = [
"BoundDatacenter",
"DatacentersPageResult",
"DatacentersClient",
]
class BoundDatacenter(BoundModelBase, Datacenter):
class BoundDatacenter(BoundModelBase[Datacenter], Datacenter):
_client: DatacentersClient _client: DatacentersClient
model = Datacenter model = Datacenter
def __init__(self, client: DatacentersClient, data: dict): def __init__(self, client: DatacentersClient, data: dict[str, Any]):
location = data.get("location") location = data.get("location")
if location is not None: if location is not None:
data["location"] = BoundLocation(client._parent.locations, location) data["location"] = BoundLocation(client._parent.locations, location)

View file

@ -8,6 +8,11 @@ if TYPE_CHECKING:
from ..locations import Location from ..locations import Location
from ..server_types import BoundServerType from ..server_types import BoundServerType
__all__ = [
"Datacenter",
"DatacenterServerTypes",
]
class Datacenter(BaseDomain, DomainIdentityMixin): class Datacenter(BaseDomain, DomainIdentityMixin):
"""Datacenter Domain """Datacenter Domain

View file

@ -2,4 +2,6 @@ from __future__ import annotations
from .domain import DeprecationInfo from .domain import DeprecationInfo
__all__ = ["DeprecationInfo"] __all__ = [
"DeprecationInfo",
]

View file

@ -1,12 +1,11 @@
from __future__ import annotations from __future__ import annotations
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain from ..core import BaseDomain
__all__ = [
"DeprecationInfo",
]
class DeprecationInfo(BaseDomain): class DeprecationInfo(BaseDomain):
"""Describes if, when & how the resources was deprecated. If this field is set to ``None`` the resource is not """Describes if, when & how the resources was deprecated. If this field is set to ``None`` the resource is not
@ -31,7 +30,5 @@ class DeprecationInfo(BaseDomain):
announced: str | None = None, announced: str | None = None,
unavailable_after: str | None = None, unavailable_after: str | None = None,
): ):
self.announced = isoparse(announced) if announced else None self.announced = self._parse_datetime(announced)
self.unavailable_after = ( self.unavailable_after = self._parse_datetime(unavailable_after)
isoparse(unavailable_after) if unavailable_after else None
)

View file

@ -5,6 +5,11 @@ library, breaking changes may occur within minor releases.
from __future__ import annotations from __future__ import annotations
__all__ = [
"is_txt_record_quoted",
"format_txt_record",
]
def is_txt_record_quoted(value: str) -> bool: def is_txt_record_quoted(value: str) -> bool:
""" """

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import ( from .domain import (
CreateFirewallResponse, CreateFirewallResponse,
@ -17,12 +24,24 @@ if TYPE_CHECKING:
from .._client import Client from .._client import Client
class BoundFirewall(BoundModelBase, Firewall): __all__ = [
"BoundFirewall",
"FirewallsPageResult",
"FirewallsClient",
]
class BoundFirewall(BoundModelBase[Firewall], Firewall):
_client: FirewallsClient _client: FirewallsClient
model = Firewall model = Firewall
def __init__(self, client: FirewallsClient, data: dict, complete: bool = True): def __init__(
self,
client: FirewallsClient,
data: dict[str, Any],
complete: bool = True,
):
rules = data.get("rules", []) rules = data.get("rules", [])
if rules: if rules:
rules = [ rules = [
@ -92,22 +111,18 @@ class BoundFirewall(BoundModelBase, Firewall):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Firewall. """
Returns a paginated list of Actions for a Firewall.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -119,17 +134,14 @@ class BoundFirewall(BoundModelBase, Firewall):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Firewall. """
Returns all Actions for a Firewall.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions( return self._client.get_actions(
self, self,
@ -193,7 +205,10 @@ class FirewallsPageResult(NamedTuple):
meta: Meta meta: Meta
class FirewallsClient(ResourceClientBase): class FirewallsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/firewalls" _base_url = "/firewalls"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -209,59 +224,40 @@ class FirewallsClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
firewall: Firewall | BoundFirewall, firewall: Firewall | BoundFirewall,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Firewall.
:param firewall: :class:`BoundFirewall <hcloud.firewalls.client.BoundFirewall>` or :class:`Firewall <hcloud.firewalls.domain.Firewall>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Firewall.
if status is not None:
params["status"] = status :param firewall: Firewall to get the Actions for.
if sort is not None: :param status: Filter the Actions by status.
params["sort"] = sort :param sort: Sort Actions by field and direction.
if page is not None: :param page: Page number to get.
params["page"] = page :param per_page: Maximum number of Actions returned per page.
if per_page is not None: """
params["per_page"] = per_page return self._get_actions_list(
response = self._client.request( f"{self._base_url}/{firewall.id}",
url=f"{self._base_url}/{firewall.id}/actions", status=status,
method="GET", sort=sort,
params=params, page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
firewall: Firewall | BoundFirewall, firewall: Firewall | BoundFirewall,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Firewall. """
Returns all Actions for a Firewall.
:param firewall: :class:`BoundFirewall <hcloud.firewalls.client.BoundFirewall>` or :class:`Firewall <hcloud.firewalls.domain.Firewall>` :param firewall: Firewall to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -2,11 +2,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING: if TYPE_CHECKING:
@ -15,6 +10,16 @@ if TYPE_CHECKING:
from .client import BoundFirewall from .client import BoundFirewall
__all__ = [
"Firewall",
"FirewallRule",
"FirewallResource",
"FirewallResourceAppliedToResources",
"FirewallResourceLabelSelector",
"CreateFirewallResponse",
]
class Firewall(BaseDomain, DomainIdentityMixin): class Firewall(BaseDomain, DomainIdentityMixin):
"""Firewall Domain """Firewall Domain
@ -49,7 +54,7 @@ class Firewall(BaseDomain, DomainIdentityMixin):
self.rules = rules self.rules = rules
self.applied_to = applied_to self.applied_to = applied_to
self.labels = labels self.labels = labels
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
class FirewallRule(BaseDomain): class FirewallRule(BaseDomain):

View file

@ -5,12 +5,13 @@ from .client import (
FloatingIPsClient, FloatingIPsClient,
FloatingIPsPageResult, FloatingIPsPageResult,
) )
from .domain import CreateFloatingIPResponse, FloatingIP from .domain import CreateFloatingIPResponse, FloatingIP, FloatingIPProtection
__all__ = [ __all__ = [
"BoundFloatingIP", "BoundFloatingIP",
"CreateFloatingIPResponse", "CreateFloatingIPResponse",
"FloatingIP", "FloatingIP",
"FloatingIPProtection",
"FloatingIPsClient", "FloatingIPsClient",
"FloatingIPsPageResult", "FloatingIPsPageResult",
] ]

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from ..locations import BoundLocation from ..locations import BoundLocation
from .domain import CreateFloatingIPResponse, FloatingIP from .domain import CreateFloatingIPResponse, FloatingIP
@ -12,13 +19,24 @@ if TYPE_CHECKING:
from ..locations import Location from ..locations import Location
from ..servers import BoundServer, Server from ..servers import BoundServer, Server
__all__ = [
"BoundFloatingIP",
"FloatingIPsPageResult",
"FloatingIPsClient",
]
class BoundFloatingIP(BoundModelBase, FloatingIP):
class BoundFloatingIP(BoundModelBase[FloatingIP], FloatingIP):
_client: FloatingIPsClient _client: FloatingIPsClient
model = FloatingIP model = FloatingIP
def __init__(self, client: FloatingIPsClient, data: dict, complete: bool = True): def __init__(
self,
client: FloatingIPsClient,
data: dict[str, Any],
complete: bool = True,
):
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from ..servers import BoundServer from ..servers import BoundServer
@ -38,22 +56,18 @@ class BoundFloatingIP(BoundModelBase, FloatingIP):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Floating IP. """
Returns a paginated list of Actions for a Floating IP.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -65,16 +79,14 @@ class BoundFloatingIP(BoundModelBase, FloatingIP):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Floating IP. """
Returns all Actions for a Floating IP.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions(self, status=status, sort=sort) return self._client.get_actions(self, status=status, sort=sort)
@ -147,7 +159,10 @@ class FloatingIPsPageResult(NamedTuple):
meta: Meta meta: Meta
class FloatingIPsClient(ResourceClientBase): class FloatingIPsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/floating_ips" _base_url = "/floating_ips"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -163,59 +178,40 @@ class FloatingIPsClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
floating_ip: FloatingIP | BoundFloatingIP, floating_ip: FloatingIP | BoundFloatingIP,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Floating IP.
:param floating_ip: :class:`BoundFloatingIP <hcloud.floating_ips.client.BoundFloatingIP>` or :class:`FloatingIP <hcloud.floating_ips.domain.FloatingIP>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Floating IP.
if status is not None:
params["status"] = status :param floating_ip: Floating IP to get the Actions for.
if sort is not None: :param status: Filter the Actions by status.
params["sort"] = sort :param sort: Sort Actions by field and direction.
if page is not None: :param page: Page number to get.
params["page"] = page :param per_page: Maximum number of Actions returned per page.
if per_page is not None: """
params["per_page"] = per_page return self._get_actions_list(
response = self._client.request( f"{self._base_url}/{floating_ip.id}",
url=f"{self._base_url}/{floating_ip.id}/actions", status=status,
method="GET", sort=sort,
params=params, page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
floating_ip: FloatingIP | BoundFloatingIP, floating_ip: FloatingIP | BoundFloatingIP,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Floating IP. """
Returns all Actions for a Floating IP.
:param floating_ip: :class:`BoundFloatingIP <hcloud.floating_ips.client.BoundFloatingIP>` or :class:`FloatingIP <hcloud.floating_ips.domain.FloatingIP>` :param floating_ip: Floating IP to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -1,21 +1,24 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING: if TYPE_CHECKING:
from ..actions import BoundAction from ..actions import BoundAction
from ..locations import BoundLocation from ..locations import BoundLocation
from ..rdns import DNSPtr
from ..servers import BoundServer from ..servers import BoundServer
from .client import BoundFloatingIP from .client import BoundFloatingIP
__all__ = [
"FloatingIP",
"FloatingIPProtection",
"CreateFloatingIPResponse",
]
class FloatingIP(BaseDomain, DomainIdentityMixin): class FloatingIP(BaseDomain, DomainIdentityMixin):
"""Floating IP Domain """Floating IP Domain
@ -68,10 +71,10 @@ class FloatingIP(BaseDomain, DomainIdentityMixin):
description: str | None = None, description: str | None = None,
ip: str | None = None, ip: str | None = None,
server: BoundServer | None = None, server: BoundServer | None = None,
dns_ptr: list[dict] | None = None, dns_ptr: list[DNSPtr] | None = None,
home_location: BoundLocation | None = None, home_location: BoundLocation | None = None,
blocked: bool | None = None, blocked: bool | None = None,
protection: dict | None = None, protection: FloatingIPProtection | None = None,
labels: dict[str, str] | None = None, labels: dict[str, str] | None = None,
created: str | None = None, created: str | None = None,
name: str | None = None, name: str | None = None,
@ -86,10 +89,14 @@ class FloatingIP(BaseDomain, DomainIdentityMixin):
self.blocked = blocked self.blocked = blocked
self.protection = protection self.protection = protection
self.labels = labels self.labels = labels
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.name = name self.name = name
class FloatingIPProtection(TypedDict):
delete: bool
class CreateFloatingIPResponse(BaseDomain): class CreateFloatingIPResponse(BaseDomain):
"""Create Floating IP Response Domain """Create Floating IP Response Domain

View file

@ -2,4 +2,6 @@ from __future__ import annotations
from .labels import LabelValidator from .labels import LabelValidator
__all__ = ["LabelValidator"] __all__ = [
"LabelValidator",
]

View file

@ -2,6 +2,10 @@ from __future__ import annotations
import re import re
__all__ = [
"LabelValidator",
]
class LabelValidator: class LabelValidator:
KEY_REGEX = re.compile( KEY_REGEX = re.compile(

View file

@ -1,12 +1,13 @@
from __future__ import annotations from __future__ import annotations
from .client import BoundImage, ImagesClient, ImagesPageResult from .client import BoundImage, ImagesClient, ImagesPageResult
from .domain import CreateImageResponse, Image from .domain import CreateImageResponse, Image, ImageProtection
__all__ = [ __all__ = [
"BoundImage", "BoundImage",
"CreateImageResponse", "CreateImageResponse",
"Image", "Image",
"ImageProtection",
"ImagesClient", "ImagesClient",
"ImagesPageResult", "ImagesPageResult",
] ]

View file

@ -3,7 +3,14 @@ from __future__ import annotations
import warnings import warnings
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import Image from .domain import Image
@ -11,12 +18,23 @@ if TYPE_CHECKING:
from .._client import Client from .._client import Client
class BoundImage(BoundModelBase, Image): __all__ = [
"BoundImage",
"ImagesPageResult",
"ImagesClient",
]
class BoundImage(BoundModelBase[Image], Image):
_client: ImagesClient _client: ImagesClient
model = Image model = Image
def __init__(self, client: ImagesClient, data: dict): def __init__(
self,
client: ImagesClient,
data: dict[str, Any],
):
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from ..servers import BoundServer from ..servers import BoundServer
@ -35,22 +53,18 @@ class BoundImage(BoundModelBase, Image):
def get_actions_list( def get_actions_list(
self, self,
sort: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
status: list[str] | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns a list of action objects for the image. """
Returns a paginated list of Actions for a Image.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -62,16 +76,14 @@ class BoundImage(BoundModelBase, Image):
def get_actions( def get_actions(
self, self,
sort: list[str] | None = None, status: list[ActionStatus] | None = None,
status: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for the image. """
Returns all Actions for a Image.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions( return self._client.get_actions(
self, self,
@ -122,7 +134,10 @@ class ImagesPageResult(NamedTuple):
meta: Meta meta: Meta
class ImagesClient(ResourceClientBase): class ImagesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/images" _base_url = "/images"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -138,64 +153,46 @@ class ImagesClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
image: Image | BoundImage, image: Image | BoundImage,
sort: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
status: list[str] | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns a list of action objects for an image.
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>` or :class:`Image <hcloud.images.domain.Image>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Image.
if sort is not None:
params["sort"] = sort :param image: Image to get the Actions for.
if status is not None: :param status: Filter the Actions by status.
params["status"] = status :param sort: Sort Actions by field and direction.
if page is not None: :param page: Page number to get.
params["page"] = page :param per_page: Maximum number of Actions returned per page.
if per_page is not None: """
params["per_page"] = per_page return self._get_actions_list(
response = self._client.request( f"{self._base_url}/{image.id}",
url=f"{self._base_url}/{image.id}/actions", status=status,
method="GET", sort=sort,
params=params, page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
image: Image | BoundImage, image: Image | BoundImage,
sort: list[str] | None = None, status: list[ActionStatus] | None = None,
status: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for an image. """
Returns all Actions for a Image.
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>` or :class:`Image <hcloud.images.domain.Image>` :param image: Image to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,
image, image,
sort=sort,
status=status, status=status,
sort=sort,
) )
def get_by_id(self, id: int) -> BoundImage: def get_by_id(self, id: int) -> BoundImage:

View file

@ -1,11 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
@ -15,6 +10,13 @@ if TYPE_CHECKING:
from .client import BoundImage from .client import BoundImage
__all__ = [
"Image",
"ImageProtection",
"CreateImageResponse",
]
class Image(BaseDomain, DomainIdentityMixin): class Image(BaseDomain, DomainIdentityMixin):
"""Image Domain """Image Domain
@ -92,18 +94,18 @@ class Image(BaseDomain, DomainIdentityMixin):
architecture: str | None = None, architecture: str | None = None,
rapid_deploy: bool | None = None, rapid_deploy: bool | None = None,
created_from: Server | BoundServer | None = None, created_from: Server | BoundServer | None = None,
protection: dict | None = None, protection: ImageProtection | None = None,
labels: dict[str, str] | None = None, labels: dict[str, str] | None = None,
status: str | None = None, status: str | None = None,
): ):
self.id = id self.id = id
self.name = name self.name = name
self.type = type self.type = type
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.description = description self.description = description
self.image_size = image_size self.image_size = image_size
self.disk_size = disk_size self.disk_size = disk_size
self.deprecated = isoparse(deprecated) if deprecated else None self.deprecated = self._parse_datetime(deprecated)
self.bound_to = bound_to self.bound_to = bound_to
self.os_flavor = os_flavor self.os_flavor = os_flavor
self.os_version = os_version self.os_version = os_version
@ -115,6 +117,10 @@ class Image(BaseDomain, DomainIdentityMixin):
self.status = status self.status = status
class ImageProtection(TypedDict):
delete: bool
class CreateImageResponse(BaseDomain): class CreateImageResponse(BaseDomain):
"""Create Image Response Domain """Create Image Response Domain

View file

@ -5,8 +5,14 @@ from typing import Any, NamedTuple
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import Iso from .domain import Iso
__all__ = [
"BoundIso",
"IsosPageResult",
"IsosClient",
]
class BoundIso(BoundModelBase, Iso):
class BoundIso(BoundModelBase[Iso], Iso):
_client: IsosClient _client: IsosClient
model = Iso model = Iso

View file

@ -1,11 +1,16 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime from datetime import datetime
from typing import Any
from warnings import warn from warnings import warn
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
from ..deprecation import DeprecationInfo from ..deprecation import DeprecationInfo
__all__ = [
"Iso",
]
class Iso(BaseDomain, DomainIdentityMixin): class Iso(BaseDomain, DomainIdentityMixin):
"""Iso Domain """Iso Domain
@ -45,7 +50,7 @@ class Iso(BaseDomain, DomainIdentityMixin):
architecture: str | None = None, architecture: str | None = None,
description: str | None = None, description: str | None = None,
deprecated: str | None = None, # pylint: disable=unused-argument deprecated: str | None = None, # pylint: disable=unused-argument
deprecation: dict | None = None, deprecation: dict[str, Any] | None = None,
): ):
self.id = id self.id = id
self.name = name self.name = name
@ -67,4 +72,4 @@ class Iso(BaseDomain, DomainIdentityMixin):
) )
if self.deprecation is None: if self.deprecation is None:
return None return None
return self.deprecation.unavailable_after return self.deprecation.unavailable_after # type: ignore[no-any-return]

View file

@ -5,8 +5,14 @@ from typing import Any, NamedTuple
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import LoadBalancerType from .domain import LoadBalancerType
__all__ = [
"BoundLoadBalancerType",
"LoadBalancerTypesPageResult",
"LoadBalancerTypesClient",
]
class BoundLoadBalancerType(BoundModelBase, LoadBalancerType):
class BoundLoadBalancerType(BoundModelBase[LoadBalancerType], LoadBalancerType):
_client: LoadBalancerTypesClient _client: LoadBalancerTypesClient
model = LoadBalancerType model = LoadBalancerType

View file

@ -1,7 +1,13 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
__all__ = [
"LoadBalancerType",
]
class LoadBalancerType(BaseDomain, DomainIdentityMixin): class LoadBalancerType(BaseDomain, DomainIdentityMixin):
"""LoadBalancerType Domain """LoadBalancerType Domain
@ -46,7 +52,7 @@ class LoadBalancerType(BaseDomain, DomainIdentityMixin):
max_services: int | None = None, max_services: int | None = None,
max_targets: int | None = None, max_targets: int | None = None,
max_assigned_certificates: int | None = None, max_assigned_certificates: int | None = None,
prices: list[dict] | None = None, prices: list[dict[str, Any]] | None = None,
): ):
self.id = id self.id = id
self.name = name self.name = name

View file

@ -15,12 +15,14 @@ from .domain import (
LoadBalancerHealtCheckHttp, LoadBalancerHealtCheckHttp,
LoadBalancerHealthCheck, LoadBalancerHealthCheck,
LoadBalancerHealthCheckHttp, LoadBalancerHealthCheckHttp,
LoadBalancerProtection,
LoadBalancerService, LoadBalancerService,
LoadBalancerServiceHttp, LoadBalancerServiceHttp,
LoadBalancerTarget, LoadBalancerTarget,
LoadBalancerTargetHealthStatus, LoadBalancerTargetHealthStatus,
LoadBalancerTargetIP, LoadBalancerTargetIP,
LoadBalancerTargetLabelSelector, LoadBalancerTargetLabelSelector,
MetricsType,
PrivateNet, PrivateNet,
PublicNetwork, PublicNetwork,
) )
@ -32,6 +34,7 @@ __all__ = [
"IPv4Address", "IPv4Address",
"IPv6Network", "IPv6Network",
"LoadBalancer", "LoadBalancer",
"LoadBalancerProtection",
"LoadBalancerAlgorithm", "LoadBalancerAlgorithm",
"LoadBalancerHealtCheckHttp", "LoadBalancerHealtCheckHttp",
"LoadBalancerHealthCheckHttp", "LoadBalancerHealthCheckHttp",
@ -46,4 +49,5 @@ __all__ = [
"LoadBalancersPageResult", "LoadBalancersPageResult",
"PrivateNet", "PrivateNet",
"PublicNetwork", "PublicNetwork",
"MetricsType",
] ]

View file

@ -8,7 +8,14 @@ try:
except ImportError: except ImportError:
isoparse = None isoparse = None
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..certificates import BoundCertificate from ..certificates import BoundCertificate
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from ..load_balancer_types import BoundLoadBalancerType from ..load_balancer_types import BoundLoadBalancerType
@ -43,13 +50,25 @@ if TYPE_CHECKING:
from ..networks import Network from ..networks import Network
class BoundLoadBalancer(BoundModelBase, LoadBalancer): __all__ = [
"BoundLoadBalancer",
"LoadBalancersPageResult",
"LoadBalancersClient",
]
class BoundLoadBalancer(BoundModelBase[LoadBalancer], LoadBalancer):
_client: LoadBalancersClient _client: LoadBalancersClient
model = LoadBalancer model = LoadBalancer
# pylint: disable=too-many-branches,too-many-locals # pylint: disable=too-many-branches,too-many-locals
def __init__(self, client: LoadBalancersClient, data: dict, complete: bool = True): def __init__(
self,
client: LoadBalancersClient,
data: dict[str, Any],
complete: bool = True,
):
algorithm = data.get("algorithm") algorithm = data.get("algorithm")
if algorithm: if algorithm:
data["algorithm"] = LoadBalancerAlgorithm(type=algorithm["type"]) data["algorithm"] = LoadBalancerAlgorithm(type=algorithm["type"])
@ -210,22 +229,18 @@ class BoundLoadBalancer(BoundModelBase, LoadBalancer):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Load Balancer. """
Returns a paginated list of Actions for a Load Balancer.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -237,16 +252,14 @@ class BoundLoadBalancer(BoundModelBase, LoadBalancer):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Load Balancer. """
Returns all Actions for a Load Balancer.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions(self, status=status, sort=sort) return self._client.get_actions(self, status=status, sort=sort)
@ -386,7 +399,10 @@ class LoadBalancersPageResult(NamedTuple):
meta: Meta meta: Meta
class LoadBalancersClient(ResourceClientBase): class LoadBalancersClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/load_balancers" _base_url = "/load_balancers"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -619,59 +635,40 @@ class LoadBalancersClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
load_balancer: LoadBalancer | BoundLoadBalancer, load_balancer: LoadBalancer | BoundLoadBalancer,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a Load Balancer.
:param load_balancer: :class:`BoundLoadBalancer <hcloud.load_balancers.client.BoundLoadBalancer>` or :class:`LoadBalancer <hcloud.load_balancers.domain.LoadBalancer>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Load Balancer.
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request( :param load_balancer: Load Balancer to get the Actions for.
url=f"{self._base_url}/{load_balancer.id}/actions", :param status: Filter the Actions by status.
method="GET", :param sort: Sort Actions by field and direction.
params=params, :param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
f"{self._base_url}/{load_balancer.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
load_balancer: LoadBalancer | BoundLoadBalancer, load_balancer: LoadBalancer | BoundLoadBalancer,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a Load Balancer. """
Returns all Actions for a Load Balancer.
:param load_balancer: :class:`BoundLoadBalancer <hcloud.load_balancers.client.BoundLoadBalancer>` or :class:`LoadBalancer <hcloud.load_balancers.domain.LoadBalancer>` :param load_balancer: Load Balancer to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -1,12 +1,7 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import TYPE_CHECKING, Any, Literal from typing import TYPE_CHECKING, Any, Literal, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
@ -21,6 +16,29 @@ if TYPE_CHECKING:
from .client import BoundLoadBalancer from .client import BoundLoadBalancer
__all__ = [
"LoadBalancer",
"LoadBalancerProtection",
"LoadBalancerService",
"LoadBalancerServiceHttp",
"LoadBalancerHealthCheck",
"LoadBalancerHealthCheckHttp",
"LoadBalancerHealtCheckHttp",
"LoadBalancerTarget",
"LoadBalancerTargetHealthStatus",
"LoadBalancerTargetLabelSelector",
"LoadBalancerTargetIP",
"LoadBalancerAlgorithm",
"PublicNetwork",
"IPv4Address",
"IPv6Network",
"PrivateNet",
"CreateLoadBalancerResponse",
"GetMetricsResponse",
"MetricsType",
]
class LoadBalancer(BaseDomain, DomainIdentityMixin): class LoadBalancer(BaseDomain, DomainIdentityMixin):
"""LoadBalancer Domain """LoadBalancer Domain
@ -86,7 +104,7 @@ class LoadBalancer(BaseDomain, DomainIdentityMixin):
algorithm: LoadBalancerAlgorithm | None = None, algorithm: LoadBalancerAlgorithm | None = None,
services: list[LoadBalancerService] | None = None, services: list[LoadBalancerService] | None = None,
load_balancer_type: BoundLoadBalancerType | None = None, load_balancer_type: BoundLoadBalancerType | None = None,
protection: dict | None = None, protection: LoadBalancerProtection | None = None,
labels: dict[str, str] | None = None, labels: dict[str, str] | None = None,
targets: list[LoadBalancerTarget] | None = None, targets: list[LoadBalancerTarget] | None = None,
created: str | None = None, created: str | None = None,
@ -96,7 +114,7 @@ class LoadBalancer(BaseDomain, DomainIdentityMixin):
): ):
self.id = id self.id = id
self.name = name self.name = name
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.public_net = public_net self.public_net = public_net
self.private_net = private_net self.private_net = private_net
self.location = location self.location = location
@ -121,6 +139,10 @@ class LoadBalancer(BaseDomain, DomainIdentityMixin):
return None return None
class LoadBalancerProtection(TypedDict):
delete: bool
class LoadBalancerService(BaseDomain): class LoadBalancerService(BaseDomain):
"""LoadBalancerService Domain """LoadBalancerService Domain
@ -339,7 +361,7 @@ class LoadBalancerHealthCheckHttp(BaseDomain):
domain: str | None = None, domain: str | None = None,
path: str | None = None, path: str | None = None,
response: str | None = None, response: str | None = None,
status_codes: list | None = None, status_codes: list[str] | None = None,
tls: bool | None = None, tls: bool | None = None,
): ):
self.domain = domain self.domain = domain
@ -362,7 +384,7 @@ class LoadBalancerHealtCheckHttp(LoadBalancerHealthCheckHttp):
domain: str | None = None, domain: str | None = None,
path: str | None = None, path: str | None = None,
response: str | None = None, response: str | None = None,
status_codes: list | None = None, status_codes: list[str] | None = None,
tls: bool | None = None, tls: bool | None = None,
): ):
warnings.warn( warnings.warn(

View file

@ -5,8 +5,14 @@ from typing import Any, NamedTuple
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import Location from .domain import Location
__all__ = [
"BoundLocation",
"LocationsPageResult",
"LocationsClient",
]
class BoundLocation(BoundModelBase, Location):
class BoundLocation(BoundModelBase[Location], Location):
_client: LocationsClient _client: LocationsClient
model = Location model = Location

View file

@ -2,6 +2,10 @@ from __future__ import annotations
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
__all__ = [
"Location",
]
class Location(BaseDomain, DomainIdentityMixin): class Location(BaseDomain, DomainIdentityMixin):
"""Location Domain """Location Domain

View file

@ -3,13 +3,14 @@ from __future__ import annotations
from datetime import datetime from datetime import datetime
from typing import Literal from typing import Literal
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain from ..core import BaseDomain
__all__ = [
"TimeSeries",
"Metrics",
]
TimeSeries = dict[str, dict[Literal["values"], list[tuple[float, str]]]] TimeSeries = dict[str, dict[Literal["values"], list[tuple[float, str]]]]
@ -44,7 +45,7 @@ class Metrics(BaseDomain):
step: float, step: float,
time_series: TimeSeries, time_series: TimeSeries,
): ):
self.start = isoparse(start) self.start = self._parse_datetime(start)
self.end = isoparse(end) self.end = self._parse_datetime(end)
self.step = step self.step = step
self.time_series = time_series self.time_series = time_series

View file

@ -4,6 +4,7 @@ from .client import BoundNetwork, NetworksClient, NetworksPageResult
from .domain import ( from .domain import (
CreateNetworkResponse, CreateNetworkResponse,
Network, Network,
NetworkProtection,
NetworkRoute, NetworkRoute,
NetworkSubnet, NetworkSubnet,
) )
@ -12,6 +13,7 @@ __all__ = [
"BoundNetwork", "BoundNetwork",
"CreateNetworkResponse", "CreateNetworkResponse",
"Network", "Network",
"NetworkProtection",
"NetworkRoute", "NetworkRoute",
"NetworkSubnet", "NetworkSubnet",
"NetworksClient", "NetworksClient",

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import Network, NetworkRoute, NetworkSubnet from .domain import Network, NetworkRoute, NetworkSubnet
@ -10,12 +17,24 @@ if TYPE_CHECKING:
from .._client import Client from .._client import Client
class BoundNetwork(BoundModelBase, Network): __all__ = [
"BoundNetwork",
"NetworksPageResult",
"NetworksClient",
]
class BoundNetwork(BoundModelBase[Network], Network):
_client: NetworksClient _client: NetworksClient
model = Network model = Network
def __init__(self, client: NetworksClient, data: dict, complete: bool = True): def __init__(
self,
client: NetworksClient,
data: dict[str, Any],
complete: bool = True,
):
subnets = data.get("subnets", []) subnets = data.get("subnets", [])
if subnets is not None: if subnets is not None:
subnets = [NetworkSubnet.from_dict(subnet) for subnet in subnets] subnets = [NetworkSubnet.from_dict(subnet) for subnet in subnets]
@ -72,22 +91,18 @@ class BoundNetwork(BoundModelBase, Network):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a network. """
Returns a paginated list of Actions for a Network.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -99,16 +114,14 @@ class BoundNetwork(BoundModelBase, Network):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a network. """
Returns all Actions for a Network.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions(self, status=status, sort=sort) return self._client.get_actions(self, status=status, sort=sort)
@ -172,7 +185,10 @@ class NetworksPageResult(NamedTuple):
meta: Meta meta: Meta
class NetworksClient(ResourceClientBase): class NetworksClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/networks" _base_url = "/networks"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -359,59 +375,40 @@ class NetworksClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
network: Network | BoundNetwork, network: Network | BoundNetwork,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a network.
:param network: :class:`BoundNetwork <hcloud.networks.client.BoundNetwork>` or :class:`Network <hcloud.networks.domain.Network>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Network.
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request( :param network: Network to get the Actions for.
url=f"{self._base_url}/{network.id}/actions", :param status: Filter the Actions by status.
method="GET", :param sort: Sort Actions by field and direction.
params=params, :param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
f"{self._base_url}/{network.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
network: Network | BoundNetwork, network: Network | BoundNetwork,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a network. """
Returns all Actions for a Network.
:param network: :class:`BoundNetwork <hcloud.networks.client.BoundNetwork>` or :class:`Network <hcloud.networks.domain.Network>` :param network: Network to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -1,12 +1,7 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
@ -15,6 +10,14 @@ if TYPE_CHECKING:
from ..servers import BoundServer from ..servers import BoundServer
from .client import BoundNetwork from .client import BoundNetwork
__all__ = [
"Network",
"NetworkProtection",
"NetworkSubnet",
"NetworkRoute",
"CreateNetworkResponse",
]
class Network(BaseDomain, DomainIdentityMixin): class Network(BaseDomain, DomainIdentityMixin):
"""Network Domain """Network Domain
@ -63,12 +66,12 @@ class Network(BaseDomain, DomainIdentityMixin):
routes: list[NetworkRoute] | None = None, routes: list[NetworkRoute] | None = None,
expose_routes_to_vswitch: bool | None = None, expose_routes_to_vswitch: bool | None = None,
servers: list[BoundServer] | None = None, servers: list[BoundServer] | None = None,
protection: dict | None = None, protection: NetworkProtection | None = None,
labels: dict[str, str] | None = None, labels: dict[str, str] | None = None,
): ):
self.id = id self.id = id
self.name = name self.name = name
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.ip_range = ip_range self.ip_range = ip_range
self.subnets = subnets self.subnets = subnets
self.routes = routes self.routes = routes
@ -78,6 +81,10 @@ class Network(BaseDomain, DomainIdentityMixin):
self.labels = labels self.labels = labels
class NetworkProtection(TypedDict):
delete: bool
class NetworkSubnet(BaseDomain): class NetworkSubnet(BaseDomain):
"""Network Subnet Domain """Network Subnet Domain
@ -116,7 +123,7 @@ class NetworkSubnet(BaseDomain):
""" """
Used to connect cloud servers and load balancers with dedicated servers. Used to connect cloud servers and load balancers with dedicated servers.
See https://docs.hetzner.com/cloud/networks/connect-dedi-vswitch/ See https://docs.hetzner.com/networking/networks/connect-dedi-vswitch/
""" """
__api_properties__ = ("type", "ip_range", "network_zone", "gateway", "vswitch_id") __api_properties__ = ("type", "ip_range", "network_zone", "gateway", "vswitch_id")

View file

@ -6,8 +6,14 @@ from ..actions import BoundAction
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import CreatePlacementGroupResponse, PlacementGroup from .domain import CreatePlacementGroupResponse, PlacementGroup
__all__ = [
"BoundPlacementGroup",
"PlacementGroupsPageResult",
"PlacementGroupsClient",
]
class BoundPlacementGroup(BoundModelBase, PlacementGroup):
class BoundPlacementGroup(BoundModelBase[PlacementGroup], PlacementGroup):
_client: PlacementGroupsClient _client: PlacementGroupsClient
model = PlacementGroup model = PlacementGroup

View file

@ -2,17 +2,17 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING: if TYPE_CHECKING:
from ..actions import BoundAction from ..actions import BoundAction
from .client import BoundPlacementGroup from .client import BoundPlacementGroup
__all__ = [
"PlacementGroup",
"CreatePlacementGroupResponse",
]
class PlacementGroup(BaseDomain, DomainIdentityMixin): class PlacementGroup(BaseDomain, DomainIdentityMixin):
"""Placement Group Domain """Placement Group Domain
@ -53,7 +53,7 @@ class PlacementGroup(BaseDomain, DomainIdentityMixin):
self.labels = labels self.labels = labels
self.servers = servers self.servers = servers
self.type = type self.type = type
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
class CreatePlacementGroupResponse(BaseDomain): class CreatePlacementGroupResponse(BaseDomain):

View file

@ -1,12 +1,13 @@
from __future__ import annotations from __future__ import annotations
from .client import BoundPrimaryIP, PrimaryIPsClient, PrimaryIPsPageResult from .client import BoundPrimaryIP, PrimaryIPsClient, PrimaryIPsPageResult
from .domain import CreatePrimaryIPResponse, PrimaryIP from .domain import CreatePrimaryIPResponse, PrimaryIP, PrimaryIPProtection
__all__ = [ __all__ = [
"BoundPrimaryIP", "BoundPrimaryIP",
"CreatePrimaryIPResponse", "CreatePrimaryIPResponse",
"PrimaryIP", "PrimaryIP",
"PrimaryIPProtection",
"PrimaryIPsClient", "PrimaryIPsClient",
"PrimaryIPsPageResult", "PrimaryIPsPageResult",
] ]

View file

@ -1,31 +1,97 @@
from __future__ import annotations from __future__ import annotations
import warnings
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import CreatePrimaryIPResponse, PrimaryIP from .domain import CreatePrimaryIPResponse, PrimaryIP
if TYPE_CHECKING: if TYPE_CHECKING:
from .._client import Client from .._client import Client
from ..datacenters import BoundDatacenter, Datacenter from ..datacenters import BoundDatacenter, Datacenter
from ..locations import BoundLocation, Location
class BoundPrimaryIP(BoundModelBase, PrimaryIP): __all__ = [
"BoundPrimaryIP",
"PrimaryIPsPageResult",
"PrimaryIPsClient",
]
class BoundPrimaryIP(BoundModelBase[PrimaryIP], PrimaryIP):
_client: PrimaryIPsClient _client: PrimaryIPsClient
model = PrimaryIP model = PrimaryIP
def __init__(self, client: PrimaryIPsClient, data: dict, complete: bool = True): def __init__(
self,
client: PrimaryIPsClient,
data: dict[str, Any],
complete: bool = True,
):
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from ..datacenters import BoundDatacenter from ..datacenters import BoundDatacenter
from ..locations import BoundLocation
datacenter = data.get("datacenter", {}) raw = data.get("datacenter", {})
if datacenter: if raw:
data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter) data["datacenter"] = BoundDatacenter(client._parent.datacenters, raw)
raw = data.get("location", {})
if raw:
data["location"] = BoundLocation(client._parent.locations, raw)
super().__init__(client, data, complete) super().__init__(client, data, complete)
def get_actions_list(
self,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""
Returns a paginated list of Actions for a Primary IP.
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
:param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._client.get_actions_list(
self,
status=status,
sort=sort,
page=page,
per_page=per_page,
)
def get_actions(
self,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
Returns all Actions for a Primary IP.
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._client.get_actions(
self,
status=status,
sort=sort,
)
def update( def update(
self, self,
auto_delete: bool | None = None, auto_delete: bool | None = None,
@ -102,7 +168,10 @@ class PrimaryIPsPageResult(NamedTuple):
meta: Meta meta: Meta
class PrimaryIPsClient(ResourceClientBase): class PrimaryIPsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/primary_ips" _base_url = "/primary_ips"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -115,6 +184,51 @@ class PrimaryIPsClient(ResourceClientBase):
super().__init__(client) super().__init__(client)
self.actions = ResourceActionsClient(client, self._base_url) self.actions = ResourceActionsClient(client, self._base_url)
def get_actions_list(
self,
primary_ip: PrimaryIP | BoundPrimaryIP,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""
Returns a paginated list of Actions for a Primary IP.
:param primary_ip: Primary IP to get the Actions for.
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
:param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
f"{self._base_url}/{primary_ip.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
)
def get_actions(
self,
primary_ip: PrimaryIP | BoundPrimaryIP,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
Returns all Actions for a Primary IP.
:param primary_ip: Primary IP to get the Actions for.
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._iter_pages(
self.get_actions_list,
primary_ip,
status=status,
sort=sort,
)
def get_by_id(self, id: int) -> BoundPrimaryIP: def get_by_id(self, id: int) -> BoundPrimaryIP:
"""Returns a specific Primary IP object. """Returns a specific Primary IP object.
@ -196,16 +310,18 @@ class PrimaryIPsClient(ResourceClientBase):
type: str, type: str,
name: str, name: str,
datacenter: Datacenter | BoundDatacenter | None = None, datacenter: Datacenter | BoundDatacenter | None = None,
location: Location | BoundLocation | None = None,
assignee_type: str | None = "server", assignee_type: str | None = "server",
assignee_id: int | None = None, assignee_id: int | None = None,
auto_delete: bool | None = False, auto_delete: bool | None = False,
labels: dict | None = None, labels: dict[str, str] | None = None,
) -> CreatePrimaryIPResponse: ) -> CreatePrimaryIPResponse:
"""Creates a new Primary IP assigned to a server. """Creates a new Primary IP assigned to a server.
:param type: str Primary IP type Choices: ipv4, ipv6 :param type: str Primary IP type Choices: ipv4, ipv6
:param name: str :param name: str
:param datacenter: Datacenter (optional) :param datacenter: Datacenter (optional)
:param location: Location (optional)
:param assignee_type: str (optional) :param assignee_type: str (optional)
:param assignee_id: int (optional) :param assignee_id: int (optional)
:param auto_delete: bool (optional) :param auto_delete: bool (optional)
@ -220,7 +336,16 @@ class PrimaryIPsClient(ResourceClientBase):
"auto_delete": auto_delete, "auto_delete": auto_delete,
} }
if datacenter is not None: if datacenter is not None:
warnings.warn(
"The 'datacenter' argument is deprecated and will be removed after 1 July 2026. "
"Please use the 'location' argument instead. "
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters",
DeprecationWarning,
stacklevel=2,
)
data["datacenter"] = datacenter.id_or_name data["datacenter"] = datacenter.id_or_name
if location is not None:
data["location"] = location.id_or_name
if assignee_id is not None: if assignee_id is not None:
data["assignee_id"] = assignee_id data["assignee_id"] = assignee_id
if labels is not None: if labels is not None:

View file

@ -1,19 +1,23 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING import warnings
from typing import TYPE_CHECKING, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING: if TYPE_CHECKING:
from ..actions import BoundAction from ..actions import BoundAction
from ..datacenters import BoundDatacenter from ..datacenters import BoundDatacenter
from ..locations import BoundLocation
from ..rdns import DNSPtr
from .client import BoundPrimaryIP from .client import BoundPrimaryIP
__all__ = [
"PrimaryIP",
"PrimaryIPProtection",
"CreatePrimaryIPResponse",
]
class PrimaryIP(BaseDomain, DomainIdentityMixin): class PrimaryIP(BaseDomain, DomainIdentityMixin):
"""Primary IP Domain """Primary IP Domain
@ -27,7 +31,15 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
:param dns_ptr: List[Dict] :param dns_ptr: List[Dict]
Array of reverse DNS entries Array of reverse DNS entries
:param datacenter: :class:`Datacenter <hcloud.datacenters.client.BoundDatacenter>` :param datacenter: :class:`Datacenter <hcloud.datacenters.client.BoundDatacenter>`
Datacenter the Primary IP was created in. Datacenter the Primary IP was created in.
This property is deprecated and will be removed after 1 July 2026.
Please use the ``location`` property instead.
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.
:param location: :class:`Location <hcloud.locations.client.BoundLocation>`
Location the Primary IP was created in.
:param blocked: boolean :param blocked: boolean
Whether the IP is blocked Whether the IP is blocked
:param protection: dict :param protection: dict
@ -46,12 +58,12 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
Delete the Primary IP when the Assignee it is assigned to is deleted. Delete the Primary IP when the Assignee it is assigned to is deleted.
""" """
__api_properties__ = ( __properties__ = (
"id", "id",
"ip", "ip",
"type", "type",
"dns_ptr", "dns_ptr",
"datacenter", "location",
"blocked", "blocked",
"protection", "protection",
"labels", "labels",
@ -61,18 +73,26 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
"assignee_type", "assignee_type",
"auto_delete", "auto_delete",
) )
__slots__ = __api_properties__ __api_properties__ = (
*__properties__,
"datacenter",
)
__slots__ = (
*__properties__,
"_datacenter",
)
def __init__( def __init__(
self, self,
id: int | None = None, id: int | None = None,
type: str | None = None, type: str | None = None,
ip: str | None = None, ip: str | None = None,
dns_ptr: list[dict] | None = None, dns_ptr: list[DNSPtr] | None = None,
datacenter: BoundDatacenter | None = None, datacenter: BoundDatacenter | None = None,
location: BoundLocation | None = None,
blocked: bool | None = None, blocked: bool | None = None,
protection: dict | None = None, protection: PrimaryIPProtection | None = None,
labels: dict[str, dict] | None = None, labels: dict[str, str] | None = None,
created: str | None = None, created: str | None = None,
name: str | None = None, name: str | None = None,
assignee_id: int | None = None, assignee_id: int | None = None,
@ -84,15 +104,38 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
self.ip = ip self.ip = ip
self.dns_ptr = dns_ptr self.dns_ptr = dns_ptr
self.datacenter = datacenter self.datacenter = datacenter
self.location = location
self.blocked = blocked self.blocked = blocked
self.protection = protection self.protection = protection
self.labels = labels self.labels = labels
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.name = name self.name = name
self.assignee_id = assignee_id self.assignee_id = assignee_id
self.assignee_type = assignee_type self.assignee_type = assignee_type
self.auto_delete = auto_delete self.auto_delete = auto_delete
@property
def datacenter(self) -> BoundDatacenter | None:
"""
:meta private:
"""
warnings.warn(
"The 'datacenter' property is deprecated and will be removed after 1 July 2026. "
"Please use the 'location' property instead. "
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.",
DeprecationWarning,
stacklevel=2,
)
return self._datacenter
@datacenter.setter
def datacenter(self, value: BoundDatacenter | None) -> None:
self._datacenter = value
class PrimaryIPProtection(TypedDict):
delete: bool
class CreatePrimaryIPResponse(BaseDomain): class CreatePrimaryIPResponse(BaseDomain):
"""Create Primary IP Response Domain """Create Primary IP Response Domain

View file

@ -0,0 +1,7 @@
from __future__ import annotations
from .domain import DNSPtr
__all__ = [
"DNSPtr",
]

View file

@ -0,0 +1,12 @@
from __future__ import annotations
from typing import TypedDict
__all__ = [
"DNSPtr",
]
class DNSPtr(TypedDict):
ip: str
dns_ptr: str

View file

@ -6,8 +6,14 @@ from ..core import BoundModelBase, Meta, ResourceClientBase
from ..locations import BoundLocation from ..locations import BoundLocation
from .domain import ServerType, ServerTypeLocation from .domain import ServerType, ServerTypeLocation
__all__ = [
"BoundServerType",
"ServerTypesPageResult",
"ServerTypesClient",
]
class BoundServerType(BoundModelBase, ServerType):
class BoundServerType(BoundModelBase[ServerType], ServerType):
_client: ServerTypesClient _client: ServerTypesClient
model = ServerType model = ServerType
@ -15,7 +21,7 @@ class BoundServerType(BoundModelBase, ServerType):
def __init__( def __init__(
self, self,
client: ServerTypesClient, client: ServerTypesClient,
data: dict, data: dict[str, Any],
complete: bool = True, complete: bool = True,
): ):
raw = data.get("locations") raw = data.get("locations")

View file

@ -1,11 +1,17 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import Any
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
from ..deprecation import DeprecationInfo from ..deprecation import DeprecationInfo
from ..locations import BoundLocation from ..locations import BoundLocation
__all__ = [
"ServerType",
"ServerTypeLocation",
]
class ServerType(BaseDomain, DomainIdentityMixin): class ServerType(BaseDomain, DomainIdentityMixin):
"""ServerType Domain """ServerType Domain
@ -79,12 +85,12 @@ class ServerType(BaseDomain, DomainIdentityMixin):
cores: int | None = None, cores: int | None = None,
memory: int | None = None, memory: int | None = None,
disk: int | None = None, disk: int | None = None,
prices: list[dict] | None = None, prices: list[dict[str, Any]] | None = None,
storage_type: str | None = None, storage_type: str | None = None,
cpu_type: str | None = None, cpu_type: str | None = None,
architecture: str | None = None, architecture: str | None = None,
deprecated: bool | None = None, deprecated: bool | None = None,
deprecation: dict | None = None, deprecation: dict[str, Any] | None = None,
included_traffic: int | None = None, included_traffic: int | None = None,
locations: list[ServerTypeLocation] | None = None, locations: list[ServerTypeLocation] | None = None,
): ):
@ -191,7 +197,7 @@ class ServerTypeLocation(BaseDomain):
self, self,
*, *,
location: BoundLocation, location: BoundLocation,
deprecation: dict | None, deprecation: dict[str, Any] | None,
): ):
self.location = location self.location = location
self.deprecation = ( self.deprecation = (

View file

@ -7,13 +7,16 @@ from .domain import (
GetMetricsResponse, GetMetricsResponse,
IPv4Address, IPv4Address,
IPv6Network, IPv6Network,
MetricsType,
PrivateNet, PrivateNet,
PublicNetwork, PublicNetwork,
PublicNetworkFirewall, PublicNetworkFirewall,
RebuildResponse,
RequestConsoleResponse, RequestConsoleResponse,
ResetPasswordResponse, ResetPasswordResponse,
Server, Server,
ServerCreatePublicNetwork, ServerCreatePublicNetwork,
ServerProtection,
) )
__all__ = [ __all__ = [
@ -29,7 +32,10 @@ __all__ = [
"RequestConsoleResponse", "RequestConsoleResponse",
"ResetPasswordResponse", "ResetPasswordResponse",
"Server", "Server",
"ServerProtection",
"ServerCreatePublicNetwork", "ServerCreatePublicNetwork",
"ServersClient", "ServersClient",
"ServersPageResult", "ServersPageResult",
"RebuildResponse",
"MetricsType",
] ]

View file

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import warnings
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
@ -8,13 +9,21 @@ try:
except ImportError: except ImportError:
isoparse = None isoparse = None
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from ..datacenters import BoundDatacenter from ..datacenters import BoundDatacenter
from ..firewalls import BoundFirewall from ..firewalls import BoundFirewall
from ..floating_ips import BoundFloatingIP from ..floating_ips import BoundFloatingIP
from ..images import BoundImage, CreateImageResponse from ..images import BoundImage, CreateImageResponse
from ..isos import BoundIso from ..isos import BoundIso
from ..locations import BoundLocation, Location
from ..metrics import Metrics from ..metrics import Metrics
from ..placement_groups import BoundPlacementGroup from ..placement_groups import BoundPlacementGroup
from ..primary_ips import BoundPrimaryIP from ..primary_ips import BoundPrimaryIP
@ -42,7 +51,6 @@ if TYPE_CHECKING:
from ..firewalls import Firewall from ..firewalls import Firewall
from ..images import Image from ..images import Image
from ..isos import Iso from ..isos import Iso
from ..locations import BoundLocation, Location
from ..networks import BoundNetwork, Network from ..networks import BoundNetwork, Network
from ..placement_groups import PlacementGroup from ..placement_groups import PlacementGroup
from ..server_types import ServerType from ..server_types import ServerType
@ -51,16 +59,32 @@ if TYPE_CHECKING:
from .domain import ServerCreatePublicNetwork from .domain import ServerCreatePublicNetwork
class BoundServer(BoundModelBase, Server): __all__ = [
"BoundServer",
"ServersPageResult",
"ServersClient",
]
class BoundServer(BoundModelBase[Server], Server):
_client: ServersClient _client: ServersClient
model = Server model = Server
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
def __init__(self, client: ServersClient, data: dict, complete: bool = True): def __init__(
datacenter = data.get("datacenter") self,
if datacenter is not None: client: ServersClient,
data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter) data: dict[str, Any],
complete: bool = True,
):
raw = data.get("datacenter")
if raw:
data["datacenter"] = BoundDatacenter(client._parent.datacenters, raw)
raw = data.get("location")
if raw:
data["location"] = BoundLocation(client._parent.locations, raw)
volumes = data.get("volumes", []) volumes = data.get("volumes", [])
if volumes: if volumes:
@ -169,22 +193,18 @@ class BoundServer(BoundModelBase, Server):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a server. """
Returns a paginated list of Actions for a Server.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -196,16 +216,14 @@ class BoundServer(BoundModelBase, Server):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a server. """
Returns all Actions for a Server.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions(self, status=status, sort=sort) return self._client.get_actions(self, status=status, sort=sort)
@ -506,7 +524,10 @@ class ServersPageResult(NamedTuple):
meta: Meta meta: Meta
class ServersClient(ResourceClientBase): class ServersClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/servers" _base_url = "/servers"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -660,6 +681,13 @@ class ServersClient(ResourceClientBase):
if location is not None: if location is not None:
data["location"] = location.id_or_name data["location"] = location.id_or_name
if datacenter is not None: if datacenter is not None:
warnings.warn(
"The 'datacenter' argument is deprecated and will be removed after 1 July 2026. "
"Please use the 'location' argument instead. "
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters",
DeprecationWarning,
stacklevel=2,
)
data["datacenter"] = datacenter.id_or_name data["datacenter"] = datacenter.id_or_name
if ssh_keys is not None: if ssh_keys is not None:
data["ssh_keys"] = [ssh_key.id_or_name for ssh_key in ssh_keys] data["ssh_keys"] = [ssh_key.id_or_name for ssh_key in ssh_keys]
@ -705,59 +733,40 @@ class ServersClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
server: Server | BoundServer, server: Server | BoundServer,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a server.
:param server: :class:`BoundServer <hcloud.servers.client.BoundServer>` or :class:`Server <hcloud.servers.domain.Server>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Server.
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request( :param server: Server to get the Actions for.
url=f"{self._base_url}/{server.id}/actions", :param status: Filter the Actions by status.
method="GET", :param sort: Sort Actions by field and direction.
params=params, :param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
f"{self._base_url}/{server.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
server: Server | BoundServer, server: Server | BoundServer,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a server. """
Returns all Actions for a Server.
:param server: :class:`BoundServer <hcloud.servers.client.BoundServer>` or :class:`Server <hcloud.servers.domain.Server>` :param server: Server to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -1,11 +1,7 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Literal import warnings
from typing import TYPE_CHECKING, Literal, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
@ -16,15 +12,36 @@ if TYPE_CHECKING:
from ..floating_ips import BoundFloatingIP from ..floating_ips import BoundFloatingIP
from ..images import BoundImage from ..images import BoundImage
from ..isos import BoundIso from ..isos import BoundIso
from ..locations import BoundLocation
from ..metrics import Metrics from ..metrics import Metrics
from ..networks import BoundNetwork, Network from ..networks import BoundNetwork, Network
from ..placement_groups import BoundPlacementGroup from ..placement_groups import BoundPlacementGroup
from ..primary_ips import BoundPrimaryIP, PrimaryIP from ..primary_ips import BoundPrimaryIP, PrimaryIP
from ..rdns import DNSPtr
from ..server_types import BoundServerType from ..server_types import BoundServerType
from ..volumes import BoundVolume from ..volumes import BoundVolume
from .client import BoundServer from .client import BoundServer
__all__ = [
"Server",
"ServerProtection",
"CreateServerResponse",
"ResetPasswordResponse",
"EnableRescueResponse",
"RequestConsoleResponse",
"RebuildResponse",
"PublicNetwork",
"PublicNetworkFirewall",
"IPv4Address",
"IPv6Network",
"PrivateNet",
"ServerCreatePublicNetwork",
"GetMetricsResponse",
"MetricsType",
]
class Server(BaseDomain, DomainIdentityMixin): class Server(BaseDomain, DomainIdentityMixin):
"""Server Domain """Server Domain
@ -40,6 +57,12 @@ class Server(BaseDomain, DomainIdentityMixin):
Public network information. Public network information.
:param server_type: :class:`BoundServerType <hcloud.server_types.client.BoundServerType>` :param server_type: :class:`BoundServerType <hcloud.server_types.client.BoundServerType>`
:param datacenter: :class:`BoundDatacenter <hcloud.datacenters.client.BoundDatacenter>` :param datacenter: :class:`BoundDatacenter <hcloud.datacenters.client.BoundDatacenter>`
This property is deprecated and will be removed after 1 July 2026.
Please use the ``location`` property instead.
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.
:param location: :class:`BoundLocation <hcloud.locations.client.BoundLocation>`
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>`, None :param image: :class:`BoundImage <hcloud.images.client.BoundImage>`, None
:param iso: :class:`BoundIso <hcloud.isos.client.BoundIso>`, None :param iso: :class:`BoundIso <hcloud.isos.client.BoundIso>`, None
:param rescue_enabled: bool :param rescue_enabled: bool
@ -85,13 +108,13 @@ class Server(BaseDomain, DomainIdentityMixin):
STATUS_UNKNOWN = "unknown" STATUS_UNKNOWN = "unknown"
"""Server Status unknown""" """Server Status unknown"""
__api_properties__ = ( __properties__ = (
"id", "id",
"name", "name",
"status", "status",
"public_net", "public_net",
"server_type", "server_type",
"datacenter", "location",
"image", "image",
"iso", "iso",
"rescue_enabled", "rescue_enabled",
@ -108,7 +131,14 @@ class Server(BaseDomain, DomainIdentityMixin):
"primary_disk_size", "primary_disk_size",
"placement_group", "placement_group",
) )
__slots__ = __api_properties__ __api_properties__ = (
*__properties__,
"datacenter",
)
__slots__ = (
*__properties__,
"_datacenter",
)
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
def __init__( def __init__(
@ -120,6 +150,7 @@ class Server(BaseDomain, DomainIdentityMixin):
public_net: PublicNetwork | None = None, public_net: PublicNetwork | None = None,
server_type: BoundServerType | None = None, server_type: BoundServerType | None = None,
datacenter: BoundDatacenter | None = None, datacenter: BoundDatacenter | None = None,
location: BoundLocation | None = None,
image: BoundImage | None = None, image: BoundImage | None = None,
iso: BoundIso | None = None, iso: BoundIso | None = None,
rescue_enabled: bool | None = None, rescue_enabled: bool | None = None,
@ -128,7 +159,7 @@ class Server(BaseDomain, DomainIdentityMixin):
outgoing_traffic: int | None = None, outgoing_traffic: int | None = None,
ingoing_traffic: int | None = None, ingoing_traffic: int | None = None,
included_traffic: int | None = None, included_traffic: int | None = None,
protection: dict | None = None, protection: ServerProtection | None = None,
labels: dict[str, str] | None = None, labels: dict[str, str] | None = None,
volumes: list[BoundVolume] | None = None, volumes: list[BoundVolume] | None = None,
private_net: list[PrivateNet] | None = None, private_net: list[PrivateNet] | None = None,
@ -138,10 +169,11 @@ class Server(BaseDomain, DomainIdentityMixin):
self.id = id self.id = id
self.name = name self.name = name
self.status = status self.status = status
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.public_net = public_net self.public_net = public_net
self.server_type = server_type self.server_type = server_type
self.datacenter = datacenter self.datacenter = datacenter
self.location = location
self.image = image self.image = image
self.iso = iso self.iso = iso
self.rescue_enabled = rescue_enabled self.rescue_enabled = rescue_enabled
@ -167,6 +199,29 @@ class Server(BaseDomain, DomainIdentityMixin):
return o return o
return None return None
@property
def datacenter(self) -> BoundDatacenter | None:
"""
:meta private:
"""
warnings.warn(
"The 'datacenter' property is deprecated and will be removed after 1 July 2026. "
"Please use the 'location' property instead. "
"See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters.",
DeprecationWarning,
stacklevel=2,
)
return self._datacenter
@datacenter.setter
def datacenter(self, value: BoundDatacenter | None) -> None:
self._datacenter = value
class ServerProtection(TypedDict):
rebuild: bool
delete: bool
class CreateServerResponse(BaseDomain): class CreateServerResponse(BaseDomain):
"""Create Server Response Domain """Create Server Response Domain
@ -392,7 +447,7 @@ class IPv6Network(BaseDomain):
self, self,
ip: str, ip: str,
blocked: bool, blocked: bool,
dns_ptr: list, dns_ptr: list[DNSPtr],
): ):
self.ip = ip self.ip = ip
self.blocked = blocked self.blocked = blocked

View file

@ -5,8 +5,14 @@ from typing import Any, NamedTuple
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import SSHKey from .domain import SSHKey
__all__ = [
"BoundSSHKey",
"SSHKeysPageResult",
"SSHKeysClient",
]
class BoundSSHKey(BoundModelBase, SSHKey):
class BoundSSHKey(BoundModelBase[SSHKey], SSHKey):
_client: SSHKeysClient _client: SSHKeysClient
model = SSHKey model = SSHKey

View file

@ -1,12 +1,11 @@
from __future__ import annotations from __future__ import annotations
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
__all__ = [
"SSHKey",
]
class SSHKey(BaseDomain, DomainIdentityMixin): class SSHKey(BaseDomain, DomainIdentityMixin):
"""SSHKey Domain """SSHKey Domain
@ -49,4 +48,4 @@ class SSHKey(BaseDomain, DomainIdentityMixin):
self.fingerprint = fingerprint self.fingerprint = fingerprint
self.public_key = public_key self.public_key = public_key
self.labels = labels self.labels = labels
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)

View file

@ -8,8 +8,14 @@ from .domain import StorageBoxType
if TYPE_CHECKING: if TYPE_CHECKING:
from .._client import Client from .._client import Client
__all__ = [
"BoundStorageBoxType",
"StorageBoxTypesPageResult",
"StorageBoxTypesClient",
]
class BoundStorageBoxType(BoundModelBase, StorageBoxType):
class BoundStorageBoxType(BoundModelBase[StorageBoxType], StorageBoxType):
_client: StorageBoxTypesClient _client: StorageBoxTypesClient
model = StorageBoxType model = StorageBoxType

View file

@ -1,8 +1,14 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
from ..deprecation import DeprecationInfo from ..deprecation import DeprecationInfo
__all__ = [
"StorageBoxType",
]
class StorageBoxType(BaseDomain, DomainIdentityMixin): class StorageBoxType(BaseDomain, DomainIdentityMixin):
""" """
@ -33,8 +39,8 @@ class StorageBoxType(BaseDomain, DomainIdentityMixin):
automatic_snapshot_limit: int | None = None, automatic_snapshot_limit: int | None = None,
subaccounts_limit: int | None = None, subaccounts_limit: int | None = None,
size: int | None = None, size: int | None = None,
prices: list[dict] | None = None, prices: list[dict[str, Any]] | None = None,
deprecation: dict | None = None, deprecation: dict[str, Any] | None = None,
): ):
self.id = id self.id = id
self.name = name self.name = name

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from ..locations import BoundLocation, Location from ..locations import BoundLocation, Location
from ..ssh_keys import BoundSSHKey, SSHKey from ..ssh_keys import BoundSSHKey, SSHKey
@ -28,8 +35,18 @@ from .domain import (
if TYPE_CHECKING: if TYPE_CHECKING:
from .._client import Client from .._client import Client
__all__ = [
"BoundStorageBox",
"BoundStorageBoxSnapshot",
"BoundStorageBoxSubaccount",
"StorageBoxesPageResult",
"StorageBoxSnapshotsPageResult",
"StorageBoxSubaccountsPageResult",
"StorageBoxesClient",
]
class BoundStorageBox(BoundModelBase, StorageBox):
class BoundStorageBox(BoundModelBase[StorageBox], StorageBox):
_client: StorageBoxesClient _client: StorageBoxesClient
model = StorageBox model = StorageBox
@ -67,20 +84,20 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_actions_list( def get_actions_list(
self, self,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
""" """
Returns a paginated list of Actions for a Storage Box for a specific page. Returns a paginated list of Actions for a Storage Box.
See https://docs.hetzner.cloud/reference/hetzner#storage-box-actions-list-actions-for-a-storage-box See https://docs.hetzner.cloud/reference/hetzner#storage-box-actions-list-actions-for-a-storage-box
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the Actions by status.
:param sort: Sort resources by field and direction. :param sort: Sort Actions by field and direction.
:param page: Page number to return. :param page: Page number to get.
:param per_page: Maximum number of entries returned per page. :param per_page: Maximum number of Actions returned per page.
Experimental: Experimental:
Storage Box support is experimental, breaking changes may occur within minor releases. Storage Box support is experimental, breaking changes may occur within minor releases.
@ -96,8 +113,8 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_actions( def get_actions(
self, self,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
""" """
Returns all Actions for a Storage Box. Returns all Actions for a Storage Box.
@ -322,7 +339,7 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_snapshot_by_name( def get_snapshot_by_name(
self, self,
name: str, name: str,
) -> BoundStorageBoxSnapshot: ) -> BoundStorageBoxSnapshot | None:
""" """
Returns a single Snapshot from a Storage Box. Returns a single Snapshot from a Storage Box.
@ -438,7 +455,7 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_subaccount_by_username( def get_subaccount_by_username(
self, self,
username: str, username: str,
) -> BoundStorageBoxSubaccount: ) -> BoundStorageBoxSubaccount | None:
""" """
Returns a single Subaccount from a Storage Box. Returns a single Subaccount from a Storage Box.
@ -537,7 +554,7 @@ class BoundStorageBox(BoundModelBase, StorageBox):
) )
class BoundStorageBoxSnapshot(BoundModelBase, StorageBoxSnapshot): class BoundStorageBoxSnapshot(BoundModelBase[StorageBoxSnapshot], StorageBoxSnapshot):
_client: StorageBoxesClient _client: StorageBoxesClient
model = StorageBoxSnapshot model = StorageBoxSnapshot
@ -561,6 +578,8 @@ class BoundStorageBoxSnapshot(BoundModelBase, StorageBoxSnapshot):
super().__init__(client, data, complete) super().__init__(client, data, complete)
def _get_self(self) -> BoundStorageBoxSnapshot: def _get_self(self) -> BoundStorageBoxSnapshot:
assert self.data_model.storage_box is not None
assert self.data_model.id is not None
return self._client.get_snapshot_by_id( return self._client.get_snapshot_by_id(
self.data_model.storage_box, self.data_model.storage_box,
self.data_model.id, self.data_model.id,
@ -603,7 +622,9 @@ class BoundStorageBoxSnapshot(BoundModelBase, StorageBoxSnapshot):
return self._client.delete_snapshot(self) return self._client.delete_snapshot(self)
class BoundStorageBoxSubaccount(BoundModelBase, StorageBoxSubaccount): class BoundStorageBoxSubaccount(
BoundModelBase[StorageBoxSubaccount], StorageBoxSubaccount
):
_client: StorageBoxesClient _client: StorageBoxesClient
model = StorageBoxSubaccount model = StorageBoxSubaccount
@ -627,6 +648,8 @@ class BoundStorageBoxSubaccount(BoundModelBase, StorageBoxSubaccount):
super().__init__(client, data, complete) super().__init__(client, data, complete)
def _get_self(self) -> BoundStorageBoxSubaccount: def _get_self(self) -> BoundStorageBoxSubaccount:
assert self.data_model.storage_box is not None
assert self.data_model.id is not None
return self._client.get_subaccount_by_id( return self._client.get_subaccount_by_id(
self.data_model.storage_box, self.data_model.storage_box,
self.data_model.id, self.data_model.id,
@ -737,7 +760,10 @@ class StorageBoxSubaccountsPageResult(NamedTuple):
meta: Meta meta: Meta
class StorageBoxesClient(ResourceClientBase): class StorageBoxesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
""" """
A client for the Storage Boxes API. A client for the Storage Boxes API.
@ -1006,58 +1032,46 @@ class StorageBoxesClient(ResourceClientBase):
self, self,
storage_box: StorageBox | BoundStorageBox, storage_box: StorageBox | BoundStorageBox,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
""" """
Returns a paginated list of Actions for a Storage Box for a specific page. Returns a paginated list of Actions for a Storage Box.
See https://docs.hetzner.cloud/reference/hetzner#storage-box-actions-list-actions-for-a-storage-box See https://docs.hetzner.cloud/reference/hetzner#storage-box-actions-list-actions-for-a-storage-box
:param storage_box: Storage Box to fetch the Actions from. :param storage_box: Storage Box to get the Actions for.
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the Actions by status.
:param sort: Sort resources by field and direction. :param sort: Sort Actions by field and direction.
:param page: Page number to return. :param page: Page number to get.
:param per_page: Maximum number of entries returned per page. :param per_page: Maximum number of Actions returned per page.
Experimental: Experimental:
Storage Box support is experimental, breaking changes may occur within minor releases. Storage Box support is experimental, breaking changes may occur within minor releases.
""" """
params: dict[str, Any] = {} return self._get_actions_list(
if status is not None: f"{self._base_url}/{storage_box.id}",
params["status"] = status status=status,
if sort is not None: sort=sort,
params["sort"] = sort page=page,
if page is not None: per_page=per_page,
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request(
method="GET",
url=f"/storage_boxes/{storage_box.id}/actions",
params=params,
)
return ActionsPageResult(
actions=[BoundAction(self._parent.actions, o) for o in response["actions"]],
meta=Meta.parse_meta(response),
) )
def get_actions( def get_actions(
self, self,
storage_box: StorageBox | BoundStorageBox, storage_box: StorageBox | BoundStorageBox,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
""" """
Returns all Actions for a Storage Box. Returns all Actions for a Storage Box.
See https://docs.hetzner.cloud/reference/hetzner#storage-box-actions-list-actions-for-a-storage-box See https://docs.hetzner.cloud/reference/hetzner#storage-box-actions-list-actions-for-a-storage-box
:param storage_box: Storage Box to fetch the Actions from. :param storage_box: Storage Box to get the Actions for.
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the actions by status. The response will only contain actions matching the specified statuses.
:param sort: Sort resources by field and direction. :param sort: Sort resources by field and direction.
@ -1279,7 +1293,7 @@ class StorageBoxesClient(ResourceClientBase):
self, self,
storage_box: StorageBox | BoundStorageBox, storage_box: StorageBox | BoundStorageBox,
name: str, name: str,
) -> BoundStorageBoxSnapshot: ) -> BoundStorageBoxSnapshot | None:
""" """
Returns a single Snapshot from a Storage Box. Returns a single Snapshot from a Storage Box.
@ -1500,7 +1514,7 @@ class StorageBoxesClient(ResourceClientBase):
self, self,
storage_box: StorageBox | BoundStorageBox, storage_box: StorageBox | BoundStorageBox,
username: str, username: str,
) -> BoundStorageBoxSubaccount: ) -> BoundStorageBoxSubaccount | None:
""" """
Returns a single Subaccount from a Storage Box. Returns a single Subaccount from a Storage Box.

View file

@ -2,11 +2,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal from typing import TYPE_CHECKING, Any, Literal
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..actions import BoundAction from ..actions import BoundAction
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
from ..locations import BoundLocation, Location from ..locations import BoundLocation, Location
@ -19,6 +14,26 @@ if TYPE_CHECKING:
BoundStorageBoxSubaccount, BoundStorageBoxSubaccount,
) )
__all__ = [
"StorageBox",
"StorageBoxAccessSettings",
"StorageBoxStats",
"StorageBoxSnapshotPlan",
"CreateStorageBoxResponse",
"DeleteStorageBoxResponse",
"StorageBoxFoldersResponse",
"StorageBoxSnapshot",
"StorageBoxSnapshotStats",
"CreateStorageBoxSnapshotResponse",
"DeleteStorageBoxSnapshotResponse",
"StorageBoxSubaccount",
"StorageBoxSubaccountAccessSettings",
"CreateStorageBoxSubaccountResponse",
"DeleteStorageBoxSubaccountResponse",
"StorageBoxStatus",
]
StorageBoxStatus = Literal[ StorageBoxStatus = Literal[
"active", "active",
"initializing", "initializing",
@ -85,7 +100,7 @@ class StorageBox(BaseDomain, DomainIdentityMixin):
self.access_settings = access_settings self.access_settings = access_settings
self.stats = stats self.stats = stats
self.status = status self.status = status
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
class StorageBoxAccessSettings(BaseDomain): class StorageBoxAccessSettings(BaseDomain):
@ -288,7 +303,7 @@ class StorageBoxSnapshot(BaseDomain, DomainIdentityMixin):
self.is_automatic = is_automatic self.is_automatic = is_automatic
self.labels = labels self.labels = labels
self.storage_box = storage_box self.storage_box = storage_box
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.stats = stats self.stats = stats
@ -389,7 +404,7 @@ class StorageBoxSubaccount(BaseDomain, DomainIdentityMixin):
self.access_settings = access_settings self.access_settings = access_settings
self.labels = labels self.labels = labels
self.storage_box = storage_box self.storage_box = storage_box
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
class StorageBoxSubaccountAccessSettings(BaseDomain): class StorageBoxSubaccountAccessSettings(BaseDomain):

View file

@ -1,12 +1,13 @@
from __future__ import annotations from __future__ import annotations
from .client import BoundVolume, VolumesClient, VolumesPageResult from .client import BoundVolume, VolumesClient, VolumesPageResult
from .domain import CreateVolumeResponse, Volume from .domain import CreateVolumeResponse, Volume, VolumeProtection
__all__ = [ __all__ = [
"BoundVolume", "BoundVolume",
"CreateVolumeResponse", "CreateVolumeResponse",
"Volume", "Volume",
"VolumeProtection",
"VolumesClient", "VolumesClient",
"VolumesPageResult", "VolumesPageResult",
] ]

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from ..locations import BoundLocation from ..locations import BoundLocation
from .domain import CreateVolumeResponse, Volume from .domain import CreateVolumeResponse, Volume
@ -13,12 +20,24 @@ if TYPE_CHECKING:
from ..servers import BoundServer, Server from ..servers import BoundServer, Server
class BoundVolume(BoundModelBase, Volume): __all__ = [
"BoundVolume",
"VolumesPageResult",
"VolumesClient",
]
class BoundVolume(BoundModelBase[Volume], Volume):
_client: VolumesClient _client: VolumesClient
model = Volume model = Volume
def __init__(self, client: VolumesClient, data: dict, complete: bool = True): def __init__(
self,
client: VolumesClient,
data: dict[str, Any],
complete: bool = True,
):
location = data.get("location") location = data.get("location")
if location is not None: if location is not None:
data["location"] = BoundLocation(client._parent.locations, location) data["location"] = BoundLocation(client._parent.locations, location)
@ -35,22 +54,18 @@ class BoundVolume(BoundModelBase, Volume):
def get_actions_list( def get_actions_list(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a volume. """
Returns a paginated list of Actions for a Volume.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional) :param page: Page number to get.
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc` :param per_page: Maximum number of Actions returned per page.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, status=status, sort=sort, page=page, per_page=per_page self, status=status, sort=sort, page=page, per_page=per_page
@ -58,16 +73,14 @@ class BoundVolume(BoundModelBase, Volume):
def get_actions( def get_actions(
self, self,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a volume. """
Returns all Actions for a Volume.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._client.get_actions(self, status=status, sort=sort) return self._client.get_actions(self, status=status, sort=sort)
@ -137,7 +150,10 @@ class VolumesPageResult(NamedTuple):
meta: Meta meta: Meta
class VolumesClient(ResourceClientBase): class VolumesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/volumes" _base_url = "/volumes"
actions: ResourceActionsClient actions: ResourceActionsClient
@ -288,59 +304,40 @@ class VolumesClient(ResourceClientBase):
def get_actions_list( def get_actions_list(
self, self,
volume: Volume | BoundVolume, volume: Volume | BoundVolume,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
"""Returns all action objects for a volume.
:param volume: :class:`BoundVolume <hcloud.volumes.client.BoundVolume>` or :class:`Volume <hcloud.volumes.domain.Volume>`
:param status: List[str] (optional)
Response will have only actions with specified statuses. Choices: `running` `success` `error`
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Specifies how many results are returned by page
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
params: dict[str, Any] = {} Returns a paginated list of Actions for a Volume.
if status is not None:
params["status"] = status
if sort is not None:
params["sort"] = sort
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request( :param volume: Volume to get the Actions for.
url=f"{self._base_url}/{volume.id}/actions", :param status: Filter the Actions by status.
method="GET", :param sort: Sort Actions by field and direction.
params=params, :param page: Page number to get.
:param per_page: Maximum number of Actions returned per page.
"""
return self._get_actions_list(
f"{self._base_url}/{volume.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
) )
actions = [
BoundAction(self._parent.actions, action_data)
for action_data in response["actions"]
]
return ActionsPageResult(actions, Meta.parse_meta(response))
def get_actions( def get_actions(
self, self,
volume: Volume | BoundVolume, volume: Volume | BoundVolume,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
"""Returns all action objects for a volume. """
Returns all Actions for a Volume.
:param volume: :class:`BoundVolume <hcloud.volumes.client.BoundVolume>` or :class:`Volume <hcloud.volumes.domain.Volume>` :param volume: Volume to get the Actions for.
:param status: List[str] (optional) :param status: Filter the Actions by status.
Response will have only actions with specified statuses. Choices: `running` `success` `error` :param sort: Sort Actions by field and direction.
:param sort: List[str] (optional)
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,

View file

@ -1,11 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
@ -15,6 +10,12 @@ if TYPE_CHECKING:
from ..servers import BoundServer, Server from ..servers import BoundServer, Server
from .client import BoundVolume from .client import BoundVolume
__all__ = [
"Volume",
"VolumeProtection",
"CreateVolumeResponse",
]
class Volume(BaseDomain, DomainIdentityMixin): class Volume(BaseDomain, DomainIdentityMixin):
"""Volume Domain """Volume Domain
@ -73,14 +74,14 @@ class Volume(BaseDomain, DomainIdentityMixin):
size: int | None = None, size: int | None = None,
linux_device: str | None = None, linux_device: str | None = None,
format: str | None = None, format: str | None = None,
protection: dict | None = None, protection: VolumeProtection | None = None,
labels: dict[str, str] | None = None, labels: dict[str, str] | None = None,
status: str | None = None, status: str | None = None,
): ):
self.id = id self.id = id
self.name = name self.name = name
self.server = server self.server = server
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.location = location self.location = location
self.size = size self.size = size
self.linux_device = linux_device self.linux_device = linux_device
@ -90,6 +91,10 @@ class Volume(BaseDomain, DomainIdentityMixin):
self.status = status self.status = status
class VolumeProtection(TypedDict):
delete: bool
class CreateVolumeResponse(BaseDomain): class CreateVolumeResponse(BaseDomain):
"""Create Volume Response Domain """Create Volume Response Domain

View file

@ -3,16 +3,26 @@ from __future__ import annotations
from .client import ( from .client import (
BoundZone, BoundZone,
BoundZoneRRSet, BoundZoneRRSet,
ZoneRRSetsPageResult,
ZonesClient, ZonesClient,
ZonesPageResult, ZonesPageResult,
) )
from .domain import ( from .domain import (
CreateZoneResponse, CreateZoneResponse,
CreateZoneRRSetResponse,
DeleteZoneResponse,
DeleteZoneRRSetResponse,
ExportZonefileResponse,
Zone, Zone,
ZoneAuthoritativeNameservers, ZoneAuthoritativeNameservers,
ZoneMode,
ZonePrimaryNameserver, ZonePrimaryNameserver,
ZoneProtection,
ZoneRecord, ZoneRecord,
ZoneRegistrar,
ZoneRRSet, ZoneRRSet,
ZoneRRSetProtection,
ZoneStatus,
) )
__all__ = [ __all__ = [
@ -26,4 +36,14 @@ __all__ = [
"ZoneRRSet", "ZoneRRSet",
"ZonesClient", "ZonesClient",
"ZonesPageResult", "ZonesPageResult",
"DeleteZoneRRSetResponse",
"ZoneRRSetProtection",
"DeleteZoneResponse",
"ZoneRegistrar",
"ZoneMode",
"ZoneRRSetsPageResult",
"ZoneProtection",
"ExportZonefileResponse",
"CreateZoneRRSetResponse",
"ZoneStatus",
] ]

View file

@ -2,7 +2,14 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient from ..actions import (
ActionSort,
ActionsPageResult,
ActionStatus,
BoundAction,
ResourceActionsClient,
)
from ..actions.client import ResourceClientBaseActionsMixin
from ..core import BoundModelBase, Meta, ResourceClientBase from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import ( from .domain import (
CreateZoneResponse, CreateZoneResponse,
@ -22,8 +29,16 @@ from .domain import (
if TYPE_CHECKING: if TYPE_CHECKING:
from .._client import Client from .._client import Client
__all__ = [
"BoundZone",
"BoundZoneRRSet",
"ZonesPageResult",
"ZoneRRSetsPageResult",
"ZonesClient",
]
class BoundZone(BoundModelBase, Zone):
class BoundZone(BoundModelBase[Zone], Zone):
_client: ZonesClient _client: ZonesClient
model = Zone model = Zone
@ -81,20 +96,20 @@ class BoundZone(BoundModelBase, Zone):
def get_actions_list( def get_actions_list(
self, self,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
""" """
Returns all Actions for the Zone for a specific page. Returns a paginated list of Actions for a Zone.
See https://docs.hetzner.cloud/reference/cloud#zones-list-zones See https://docs.hetzner.cloud/reference/cloud#zones-list-zones
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the Actions by status.
:param sort: Sort resources by field and direction. :param sort: Sort Actions by field and direction.
:param page: Page number to return. :param page: Page number to get.
:param per_page: Maximum number of entries returned per page. :param per_page: Maximum number of Actions returned per page.
""" """
return self._client.get_actions_list( return self._client.get_actions_list(
self, self,
@ -107,16 +122,16 @@ class BoundZone(BoundModelBase, Zone):
def get_actions( def get_actions(
self, self,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
""" """
Returns all Actions for the Zone. Returns all Actions for a Zone.
See https://docs.hetzner.cloud/reference/cloud#zones-list-zones See https://docs.hetzner.cloud/reference/cloud#zones-list-zones
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the Actions by status.
:param sort: Sort resources by field and direction. :param sort: Sort Actions by field and direction.
""" """
return self._client.get_actions( return self._client.get_actions(
self, self,
@ -367,7 +382,7 @@ class BoundZone(BoundModelBase, Zone):
""" """
Updates records in a ZoneRRSet. Updates records in a ZoneRRSet.
See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-to-an-rrset See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-of-an-rrset
:param rrset: RRSet to update. :param rrset: RRSet to update.
:param records: Records to update in the RRSet. :param records: Records to update in the RRSet.
@ -405,12 +420,17 @@ class BoundZone(BoundModelBase, Zone):
return self._client.set_rrset_records(rrset=rrset, records=records) return self._client.set_rrset_records(rrset=rrset, records=records)
class BoundZoneRRSet(BoundModelBase, ZoneRRSet): class BoundZoneRRSet(BoundModelBase[ZoneRRSet], ZoneRRSet):
_client: ZonesClient _client: ZonesClient
model = ZoneRRSet model = ZoneRRSet
def __init__(self, client: ZonesClient, data: dict, complete: bool = True): def __init__(
self,
client: ZonesClient,
data: dict[str, Any],
complete: bool = True,
):
raw = data.get("zone") raw = data.get("zone")
if raw is not None: if raw is not None:
data["zone"] = BoundZone(client, data={"id": raw}, complete=False) data["zone"] = BoundZone(client, data={"id": raw}, complete=False)
@ -422,6 +442,8 @@ class BoundZoneRRSet(BoundModelBase, ZoneRRSet):
super().__init__(client, data, complete) super().__init__(client, data, complete)
def _get_self(self) -> BoundZoneRRSet: def _get_self(self) -> BoundZoneRRSet:
assert self.data_model.zone is not None
assert self.data_model.type is not None
return self._client.get_rrset( return self._client.get_rrset(
self.data_model.zone, self.data_model.zone,
self.data_model.name, self.data_model.name,
@ -501,7 +523,7 @@ class BoundZoneRRSet(BoundModelBase, ZoneRRSet):
""" """
Updates records in a ZoneRRSet. Updates records in a ZoneRRSet.
See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-to-an-rrset See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-of-an-rrset
:param records: Records to update in the RRSet. :param records: Records to update in the RRSet.
""" """
@ -544,7 +566,10 @@ class ZoneRRSetsPageResult(NamedTuple):
meta: Meta meta: Meta
class ZonesClient(ResourceClientBase): class ZonesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
""" """
ZoneClient is a client for the Zone (DNS) API. ZoneClient is a client for the Zone (DNS) API.
@ -767,57 +792,45 @@ class ZonesClient(ResourceClientBase):
self, self,
zone: Zone | BoundZone, zone: Zone | BoundZone,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
) -> ActionsPageResult: ) -> ActionsPageResult:
""" """
Returns all Actions for a Zone for a specific page. Returns a paginated list of Actions for a Zone.
See https://docs.hetzner.cloud/reference/cloud#zones-list-zones See https://docs.hetzner.cloud/reference/cloud#zones-list-zones
:param zone: Zone to fetch the Actions from. :param zone: Zone to get the Actions for.
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the Actions by status.
:param sort: Sort resources by field and direction. :param sort: Sort Actions by field and direction.
:param page: Page number to return. :param page: Page number to get.
:param per_page: Maximum number of entries returned per page. :param per_page: Maximum number of Actions returned per page.
""" """
params: dict[str, Any] = {} return self._get_actions_list(
if status is not None: f"{self._base_url}/{zone.id_or_name}",
params["status"] = status status=status,
if sort is not None: sort=sort,
params["sort"] = sort page=page,
if page is not None: per_page=per_page,
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request(
method="GET",
url=f"{self._base_url}/{zone.id_or_name}/actions",
params=params,
)
return ActionsPageResult(
actions=[BoundAction(self._parent.actions, o) for o in response["actions"]],
meta=Meta.parse_meta(response),
) )
def get_actions( def get_actions(
self, self,
zone: Zone | BoundZone, zone: Zone | BoundZone,
*, *,
status: list[str] | None = None, status: list[ActionStatus] | None = None,
sort: list[str] | None = None, sort: list[ActionSort] | None = None,
) -> list[BoundAction]: ) -> list[BoundAction]:
""" """
Returns all Actions for a Zone. Returns all Actions for a Zone.
See https://docs.hetzner.cloud/reference/cloud#zones-list-zones See https://docs.hetzner.cloud/reference/cloud#zones-list-zones
:param zone: Zone to fetch the Actions from. :param zone: Zone to get the Actions for.
:param status: Filter the actions by status. The response will only contain actions matching the specified statuses. :param status: Filter the Actions by status.
:param sort: Sort resources by field and direction. :param sort: Sort Actions by field and direction.
""" """
return self._iter_pages( return self._iter_pages(
self.get_actions_list, self.get_actions_list,
@ -1208,7 +1221,7 @@ class ZonesClient(ResourceClientBase):
""" """
Updates records in a ZoneRRSet. Updates records in a ZoneRRSet.
See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-to-an-rrset See https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-update-records-of-an-rrset
:param rrset: RRSet to update. :param rrset: RRSet to update.
:param records: Records to update in the RRSet. :param records: Records to update in the RRSet.

View file

@ -2,11 +2,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, TypedDict from typing import TYPE_CHECKING, Any, Literal, TypedDict
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING: if TYPE_CHECKING:
@ -14,6 +9,24 @@ if TYPE_CHECKING:
from .client import BoundZone, BoundZoneRRSet from .client import BoundZone, BoundZoneRRSet
__all__ = [
"ZoneMode",
"ZoneStatus",
"ZoneRegistrar",
"Zone",
"ZonePrimaryNameserver",
"ZoneAuthoritativeNameservers",
"ZoneProtection",
"CreateZoneResponse",
"DeleteZoneResponse",
"ExportZonefileResponse",
"ZoneRRSet",
"ZoneRRSetProtection",
"ZoneRecord",
"CreateZoneRRSetResponse",
"DeleteZoneRRSetResponse",
]
ZoneMode = Literal["primary", "secondary"] ZoneMode = Literal["primary", "secondary"]
ZoneStatus = Literal["ok", "updating", "error"] ZoneStatus = Literal["ok", "updating", "error"]
ZoneRegistrar = Literal["hetzner", "other", "unknown"] ZoneRegistrar = Literal["hetzner", "other", "unknown"]
@ -81,7 +94,7 @@ class Zone(BaseDomain, DomainIdentityMixin):
): ):
self.id = id self.id = id
self.name = name self.name = name
self.created = isoparse(created) if created else None self.created = self._parse_datetime(created)
self.mode = mode self.mode = mode
self.ttl = ttl self.ttl = ttl
self.labels = labels self.labels = labels
@ -188,11 +201,7 @@ class ZoneAuthoritativeNameservers(BaseDomain):
): ):
self.assigned = assigned self.assigned = assigned
self.delegated = delegated self.delegated = delegated
self.delegation_last_check = ( self.delegation_last_check = self._parse_datetime(delegation_last_check)
isoparse(delegation_last_check)
if delegation_last_check is not None
else None
)
self.delegation_status = delegation_status self.delegation_status = delegation_status

View file

@ -22,7 +22,7 @@ from textwrap import dedent
logger = logging.getLogger("vendor") logger = logging.getLogger("vendor")
HCLOUD_SOURCE_URL = "https://github.com/hetznercloud/hcloud-python" HCLOUD_SOURCE_URL = "https://github.com/hetznercloud/hcloud-python"
HCLOUD_VERSION = "v2.12.0" HCLOUD_VERSION = "v2.13.0"
HCLOUD_VENDOR_PATH = "plugins/module_utils/vendor/hcloud" HCLOUD_VENDOR_PATH = "plugins/module_utils/vendor/hcloud"