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 ._client import ( # noqa pylint: disable=C0414
Client as Client,
constant_backoff_function as constant_backoff_function,
exponential_backoff_function as exponential_backoff_function,
from ._client import (
Client,
constant_backoff_function,
exponential_backoff_function,
)
from ._exceptions import ( # noqa pylint: disable=C0414
APIException as APIException,
HCloudException as HCloudException,
)
from ._version import __version__ # noqa
from ._exceptions import APIException, HCloudException
from ._version import __version__
__all__ = [
"__version__",
"Client",
"constant_backoff_function",
"exponential_backoff_function",
"APIException",
"HCloudException",
]

View file

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

View file

@ -1,3 +1,3 @@
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 (
ActionsClient,
ActionSort,
ActionsPageResult,
BoundAction,
ResourceActionsClient,
)
from .domain import (
Action,
ActionError,
ActionException,
ActionFailedException,
ActionResource,
ActionStatus,
ActionTimeoutException,
)
__all__ = [
"Action",
"ActionException",
"ActionFailedException",
"ActionTimeoutException",
"ActionsClient",
"ActionsPageResult",
"BoundAction",
"ResourceActionsClient",
"ActionSort",
"ActionStatus",
"Action",
"ActionResource",
"ActionError",
"ActionException",
"ActionFailedException",
"ActionTimeoutException",
]

View file

@ -2,16 +2,25 @@ from __future__ import annotations
import time
import warnings
from typing import TYPE_CHECKING, Any, NamedTuple
from typing import TYPE_CHECKING, Any, Literal, NamedTuple
from ..core import BoundModelBase, Meta, ResourceClientBase
from .domain import Action, ActionFailedException, ActionTimeoutException
from .domain import Action, ActionFailedException, ActionStatus, ActionTimeoutException
if TYPE_CHECKING:
from .._client import Client
class BoundAction(BoundModelBase, Action):
__all__ = [
"ActionsClient",
"ActionsPageResult",
"BoundAction",
"ResourceActionsClient",
"ActionSort",
]
class BoundAction(BoundModelBase[Action], Action):
_client: ActionsClient
model = Action
@ -45,12 +54,78 @@ class BoundAction(BoundModelBase, Action):
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):
actions: list[BoundAction]
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
def __init__(self, client: ResourceClientBase | Client, resource: str | None):
@ -66,69 +141,46 @@ class ResourceActionsClient(ResourceClientBase):
self._resource = resource or ""
def get_by_id(self, id: int) -> BoundAction:
"""Get a specific action by its ID.
:param id: int
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
"""
response = self._client.request(
url=f"{self._resource}/actions/{id}",
method="GET",
)
return BoundAction(self._parent.actions, response["action"])
Returns a specific Action by its ID.
:param id: ID of the Action.
"""
return self._get_action_by_id(self._resource, id)
def get_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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
Returns a paginated list of Actions.
response = self._client.request(
url=f"{self._resource}/actions",
method="GET",
params=params,
: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(
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(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Get all actions.
"""
Returns all 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)
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._iter_pages(self.get_list, status=status, sort=sort)
@ -139,8 +191,8 @@ class ActionsClient(ResourceActionsClient):
def get_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
@ -162,8 +214,8 @@ class ActionsClient(ResourceActionsClient):
def get_all(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
.. deprecated:: 1.28

View file

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

View file

@ -2,7 +2,14 @@ from __future__ import annotations
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 .domain import (
Certificate,
@ -15,12 +22,24 @@ if TYPE_CHECKING:
from .._client import Client
class BoundCertificate(BoundModelBase, Certificate):
__all__ = [
"BoundCertificate",
"CertificatesPageResult",
"CertificatesClient",
]
class BoundCertificate(BoundModelBase[Certificate], Certificate):
_client: CertificatesClient
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")
if status is not None:
error_data = status.get("error")
@ -36,22 +55,18 @@ class BoundCertificate(BoundModelBase, Certificate):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a Certificate.
"""
Returns a paginated list of Actions for a 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>`)
: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,
@ -63,16 +78,14 @@ class BoundCertificate(BoundModelBase, Certificate):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a Certificate.
"""
Returns all Actions for a 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._client.get_actions(
self,
@ -117,7 +130,10 @@ class CertificatesPageResult(NamedTuple):
meta: Meta
class CertificatesClient(ResourceClientBase):
class CertificatesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/certificates"
actions: ResourceActionsClient
@ -306,59 +322,40 @@ class CertificatesClient(ResourceClientBase):
def get_actions_list(
self,
certificate: Certificate | BoundCertificate,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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
Returns a paginated list of Actions for a Certificate.
response = self._client.request(
url=f"{self._base_url}/{certificate.id}/actions",
method="GET",
params=params,
:param certificate: Certificate 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}/{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(
self,
certificate: Certificate | BoundCertificate,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param certificate: Certificate 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,

View file

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

View file

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

View file

@ -1,13 +1,26 @@
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:
__api_properties__: tuple
__api_properties__: tuple[str, ...]
@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.
"""
@ -15,7 +28,7 @@ class BaseDomain:
return cls(**supported_data)
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)})"
def __eq__(self, other: Any) -> bool:
@ -27,6 +40,16 @@ class BaseDomain:
return False
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:
@ -106,7 +129,7 @@ class Meta(BaseDomain):
self.pagination = pagination
@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.
"""

View file

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

View file

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

View file

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

View file

@ -1,12 +1,11 @@
from __future__ import annotations
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain
__all__ = [
"DeprecationInfo",
]
class DeprecationInfo(BaseDomain):
"""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,
unavailable_after: str | None = None,
):
self.announced = isoparse(announced) if announced else None
self.unavailable_after = (
isoparse(unavailable_after) if unavailable_after else None
)
self.announced = self._parse_datetime(announced)
self.unavailable_after = self._parse_datetime(unavailable_after)

View file

@ -5,6 +5,11 @@ library, breaking changes may occur within minor releases.
from __future__ import annotations
__all__ = [
"is_txt_record_quoted",
"format_txt_record",
]
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 ..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 .domain import (
CreateFirewallResponse,
@ -17,12 +24,24 @@ if TYPE_CHECKING:
from .._client import Client
class BoundFirewall(BoundModelBase, Firewall):
__all__ = [
"BoundFirewall",
"FirewallsPageResult",
"FirewallsClient",
]
class BoundFirewall(BoundModelBase[Firewall], Firewall):
_client: FirewallsClient
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", [])
if rules:
rules = [
@ -92,22 +111,18 @@ class BoundFirewall(BoundModelBase, Firewall):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a Firewall.
"""
Returns a paginated list of Actions for a 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>`)
: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,
@ -119,17 +134,14 @@ class BoundFirewall(BoundModelBase, Firewall):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a Firewall.
"""
Returns all Actions for a 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._client.get_actions(
self,
@ -193,7 +205,10 @@ class FirewallsPageResult(NamedTuple):
meta: Meta
class FirewallsClient(ResourceClientBase):
class FirewallsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/firewalls"
actions: ResourceActionsClient
@ -209,59 +224,40 @@ class FirewallsClient(ResourceClientBase):
def get_actions_list(
self,
firewall: Firewall | BoundFirewall,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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(
url=f"{self._base_url}/{firewall.id}/actions",
method="GET",
params=params,
Returns a paginated list of Actions for a Firewall.
:param firewall: Firewall 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}/{firewall.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(
self,
firewall: Firewall | BoundFirewall,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param firewall: Firewall 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,

View file

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

View file

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

View file

@ -2,7 +2,14 @@ from __future__ import annotations
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 ..locations import BoundLocation
from .domain import CreateFloatingIPResponse, FloatingIP
@ -12,13 +19,24 @@ if TYPE_CHECKING:
from ..locations import Location
from ..servers import BoundServer, Server
__all__ = [
"BoundFloatingIP",
"FloatingIPsPageResult",
"FloatingIPsClient",
]
class BoundFloatingIP(BoundModelBase, FloatingIP):
class BoundFloatingIP(BoundModelBase[FloatingIP], FloatingIP):
_client: FloatingIPsClient
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
from ..servers import BoundServer
@ -38,22 +56,18 @@ class BoundFloatingIP(BoundModelBase, FloatingIP):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a Floating IP.
"""
Returns a paginated list of Actions for a Floating IP.
: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>`)
: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,
@ -65,16 +79,14 @@ class BoundFloatingIP(BoundModelBase, FloatingIP):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a Floating IP.
"""
Returns all Actions for a Floating IP.
: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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
: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)
@ -147,7 +159,10 @@ class FloatingIPsPageResult(NamedTuple):
meta: Meta
class FloatingIPsClient(ResourceClientBase):
class FloatingIPsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/floating_ips"
actions: ResourceActionsClient
@ -163,59 +178,40 @@ class FloatingIPsClient(ResourceClientBase):
def get_actions_list(
self,
floating_ip: FloatingIP | BoundFloatingIP,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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(
url=f"{self._base_url}/{floating_ip.id}/actions",
method="GET",
params=params,
Returns a paginated list of Actions for a Floating IP.
:param floating_ip: Floating 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}/{floating_ip.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(
self,
floating_ip: FloatingIP | BoundFloatingIP,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param floating_ip: Floating 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,

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,14 @@ from __future__ import annotations
import warnings
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 .domain import Image
@ -11,12 +18,23 @@ if TYPE_CHECKING:
from .._client import Client
class BoundImage(BoundModelBase, Image):
__all__ = [
"BoundImage",
"ImagesPageResult",
"ImagesClient",
]
class BoundImage(BoundModelBase[Image], Image):
_client: ImagesClient
model = Image
def __init__(self, client: ImagesClient, data: dict):
def __init__(
self,
client: ImagesClient,
data: dict[str, Any],
):
# pylint: disable=import-outside-toplevel
from ..servers import BoundServer
@ -35,22 +53,18 @@ class BoundImage(BoundModelBase, Image):
def get_actions_list(
self,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
status: list[str] | None = None,
) -> ActionsPageResult:
"""Returns a list of action objects for the image.
"""
Returns a paginated list of Actions for a 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>`)
: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,
@ -62,16 +76,14 @@ class BoundImage(BoundModelBase, Image):
def get_actions(
self,
sort: list[str] | None = None,
status: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for the image.
"""
Returns all Actions for a 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._client.get_actions(
self,
@ -122,7 +134,10 @@ class ImagesPageResult(NamedTuple):
meta: Meta
class ImagesClient(ResourceClientBase):
class ImagesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/images"
actions: ResourceActionsClient
@ -138,64 +153,46 @@ class ImagesClient(ResourceClientBase):
def get_actions_list(
self,
image: Image | BoundImage,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
status: list[str] | None = None,
) -> 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] = {}
if sort is not None:
params["sort"] = sort
if status is not None:
params["status"] = status
if page is not None:
params["page"] = page
if per_page is not None:
params["per_page"] = per_page
response = self._client.request(
url=f"{self._base_url}/{image.id}/actions",
method="GET",
params=params,
Returns a paginated list of Actions for a Image.
:param image: Image 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}/{image.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(
self,
image: Image | BoundImage,
sort: list[str] | None = None,
status: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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)
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param image: Image 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,
image,
sort=sort,
status=status,
sort=sort,
)
def get_by_id(self, id: int) -> BoundImage:

View file

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

View file

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

View file

@ -1,11 +1,16 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from warnings import warn
from ..core import BaseDomain, DomainIdentityMixin
from ..deprecation import DeprecationInfo
__all__ = [
"Iso",
]
class Iso(BaseDomain, DomainIdentityMixin):
"""Iso Domain
@ -45,7 +50,7 @@ class Iso(BaseDomain, DomainIdentityMixin):
architecture: str | None = None,
description: str | None = None,
deprecated: str | None = None, # pylint: disable=unused-argument
deprecation: dict | None = None,
deprecation: dict[str, Any] | None = None,
):
self.id = id
self.name = name
@ -67,4 +72,4 @@ class Iso(BaseDomain, DomainIdentityMixin):
)
if self.deprecation is 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 .domain import LoadBalancerType
__all__ = [
"BoundLoadBalancerType",
"LoadBalancerTypesPageResult",
"LoadBalancerTypesClient",
]
class BoundLoadBalancerType(BoundModelBase, LoadBalancerType):
class BoundLoadBalancerType(BoundModelBase[LoadBalancerType], LoadBalancerType):
_client: LoadBalancerTypesClient
model = LoadBalancerType

View file

@ -1,7 +1,13 @@
from __future__ import annotations
from typing import Any
from ..core import BaseDomain, DomainIdentityMixin
__all__ = [
"LoadBalancerType",
]
class LoadBalancerType(BaseDomain, DomainIdentityMixin):
"""LoadBalancerType Domain
@ -46,7 +52,7 @@ class LoadBalancerType(BaseDomain, DomainIdentityMixin):
max_services: int | None = None,
max_targets: 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.name = name

View file

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

View file

@ -8,7 +8,14 @@ try:
except ImportError:
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 ..core import BoundModelBase, Meta, ResourceClientBase
from ..load_balancer_types import BoundLoadBalancerType
@ -43,13 +50,25 @@ if TYPE_CHECKING:
from ..networks import Network
class BoundLoadBalancer(BoundModelBase, LoadBalancer):
__all__ = [
"BoundLoadBalancer",
"LoadBalancersPageResult",
"LoadBalancersClient",
]
class BoundLoadBalancer(BoundModelBase[LoadBalancer], LoadBalancer):
_client: LoadBalancersClient
model = LoadBalancer
# 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")
if algorithm:
data["algorithm"] = LoadBalancerAlgorithm(type=algorithm["type"])
@ -210,22 +229,18 @@ class BoundLoadBalancer(BoundModelBase, LoadBalancer):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a Load Balancer.
"""
Returns a paginated list of Actions for a Load Balancer.
: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>`)
: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,
@ -237,16 +252,14 @@ class BoundLoadBalancer(BoundModelBase, LoadBalancer):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a Load Balancer.
"""
Returns all Actions for a Load Balancer.
: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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
: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)
@ -386,7 +399,10 @@ class LoadBalancersPageResult(NamedTuple):
meta: Meta
class LoadBalancersClient(ResourceClientBase):
class LoadBalancersClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/load_balancers"
actions: ResourceActionsClient
@ -619,59 +635,40 @@ class LoadBalancersClient(ResourceClientBase):
def get_actions_list(
self,
load_balancer: LoadBalancer | BoundLoadBalancer,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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
Returns a paginated list of Actions for a Load Balancer.
response = self._client.request(
url=f"{self._base_url}/{load_balancer.id}/actions",
method="GET",
params=params,
:param load_balancer: Load Balancer 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}/{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(
self,
load_balancer: LoadBalancer | BoundLoadBalancer,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param load_balancer: Load Balancer 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,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,14 @@ from __future__ import annotations
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 .domain import Network, NetworkRoute, NetworkSubnet
@ -10,12 +17,24 @@ if TYPE_CHECKING:
from .._client import Client
class BoundNetwork(BoundModelBase, Network):
__all__ = [
"BoundNetwork",
"NetworksPageResult",
"NetworksClient",
]
class BoundNetwork(BoundModelBase[Network], Network):
_client: NetworksClient
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", [])
if subnets is not None:
subnets = [NetworkSubnet.from_dict(subnet) for subnet in subnets]
@ -72,22 +91,18 @@ class BoundNetwork(BoundModelBase, Network):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a network.
"""
Returns a paginated list of Actions for a 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>`)
: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,
@ -99,16 +114,14 @@ class BoundNetwork(BoundModelBase, Network):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a network.
"""
Returns all Actions for a 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
: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)
@ -172,7 +185,10 @@ class NetworksPageResult(NamedTuple):
meta: Meta
class NetworksClient(ResourceClientBase):
class NetworksClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/networks"
actions: ResourceActionsClient
@ -359,59 +375,40 @@ class NetworksClient(ResourceClientBase):
def get_actions_list(
self,
network: Network | BoundNetwork,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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
Returns a paginated list of Actions for a Network.
response = self._client.request(
url=f"{self._base_url}/{network.id}/actions",
method="GET",
params=params,
:param network: Network 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}/{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(
self,
network: Network | BoundNetwork,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param network: Network 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,

View file

@ -1,12 +1,7 @@
from __future__ import annotations
import warnings
from typing import TYPE_CHECKING
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from typing import TYPE_CHECKING, TypedDict
from ..core import BaseDomain, DomainIdentityMixin
@ -15,6 +10,14 @@ if TYPE_CHECKING:
from ..servers import BoundServer
from .client import BoundNetwork
__all__ = [
"Network",
"NetworkProtection",
"NetworkSubnet",
"NetworkRoute",
"CreateNetworkResponse",
]
class Network(BaseDomain, DomainIdentityMixin):
"""Network Domain
@ -63,12 +66,12 @@ class Network(BaseDomain, DomainIdentityMixin):
routes: list[NetworkRoute] | None = None,
expose_routes_to_vswitch: bool | None = None,
servers: list[BoundServer] | None = None,
protection: dict | None = None,
protection: NetworkProtection | None = None,
labels: dict[str, str] | None = None,
):
self.id = id
self.name = name
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
self.ip_range = ip_range
self.subnets = subnets
self.routes = routes
@ -78,6 +81,10 @@ class Network(BaseDomain, DomainIdentityMixin):
self.labels = labels
class NetworkProtection(TypedDict):
delete: bool
class NetworkSubnet(BaseDomain):
"""Network Subnet Domain
@ -116,7 +123,7 @@ class NetworkSubnet(BaseDomain):
"""
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")

View file

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

View file

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

View file

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

View file

@ -1,31 +1,97 @@
from __future__ import annotations
import warnings
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 .domain import CreatePrimaryIPResponse, PrimaryIP
if TYPE_CHECKING:
from .._client import Client
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
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
from ..datacenters import BoundDatacenter
from ..locations import BoundLocation
datacenter = data.get("datacenter", {})
if datacenter:
data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter)
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)
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(
self,
auto_delete: bool | None = None,
@ -102,7 +168,10 @@ class PrimaryIPsPageResult(NamedTuple):
meta: Meta
class PrimaryIPsClient(ResourceClientBase):
class PrimaryIPsClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/primary_ips"
actions: ResourceActionsClient
@ -115,6 +184,51 @@ class PrimaryIPsClient(ResourceClientBase):
super().__init__(client)
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:
"""Returns a specific Primary IP object.
@ -196,16 +310,18 @@ class PrimaryIPsClient(ResourceClientBase):
type: str,
name: str,
datacenter: Datacenter | BoundDatacenter | None = None,
location: Location | BoundLocation | None = None,
assignee_type: str | None = "server",
assignee_id: int | None = None,
auto_delete: bool | None = False,
labels: dict | None = None,
labels: dict[str, str] | None = None,
) -> CreatePrimaryIPResponse:
"""Creates a new Primary IP assigned to a server.
:param type: str Primary IP type Choices: ipv4, ipv6
:param name: str
:param datacenter: Datacenter (optional)
:param location: Location (optional)
:param assignee_type: str (optional)
:param assignee_id: int (optional)
:param auto_delete: bool (optional)
@ -220,7 +336,16 @@ class PrimaryIPsClient(ResourceClientBase):
"auto_delete": auto_delete,
}
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
if location is not None:
data["location"] = location.id_or_name
if assignee_id is not None:
data["assignee_id"] = assignee_id
if labels is not None:

View file

@ -1,19 +1,23 @@
from __future__ import annotations
from typing import TYPE_CHECKING
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
import warnings
from typing import TYPE_CHECKING, TypedDict
from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING:
from ..actions import BoundAction
from ..datacenters import BoundDatacenter
from ..locations import BoundLocation
from ..rdns import DNSPtr
from .client import BoundPrimaryIP
__all__ = [
"PrimaryIP",
"PrimaryIPProtection",
"CreatePrimaryIPResponse",
]
class PrimaryIP(BaseDomain, DomainIdentityMixin):
"""Primary IP Domain
@ -27,7 +31,15 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
:param dns_ptr: List[Dict]
Array of reverse DNS entries
: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
Whether the IP is blocked
:param protection: dict
@ -46,12 +58,12 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
Delete the Primary IP when the Assignee it is assigned to is deleted.
"""
__api_properties__ = (
__properties__ = (
"id",
"ip",
"type",
"dns_ptr",
"datacenter",
"location",
"blocked",
"protection",
"labels",
@ -61,18 +73,26 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
"assignee_type",
"auto_delete",
)
__slots__ = __api_properties__
__api_properties__ = (
*__properties__,
"datacenter",
)
__slots__ = (
*__properties__,
"_datacenter",
)
def __init__(
self,
id: int | None = None,
type: str | None = None,
ip: str | None = None,
dns_ptr: list[dict] | None = None,
dns_ptr: list[DNSPtr] | None = None,
datacenter: BoundDatacenter | None = None,
location: BoundLocation | None = None,
blocked: bool | None = None,
protection: dict | None = None,
labels: dict[str, dict] | None = None,
protection: PrimaryIPProtection | None = None,
labels: dict[str, str] | None = None,
created: str | None = None,
name: str | None = None,
assignee_id: int | None = None,
@ -84,15 +104,38 @@ class PrimaryIP(BaseDomain, DomainIdentityMixin):
self.ip = ip
self.dns_ptr = dns_ptr
self.datacenter = datacenter
self.location = location
self.blocked = blocked
self.protection = protection
self.labels = labels
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
self.name = name
self.assignee_id = assignee_id
self.assignee_type = assignee_type
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):
"""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 .domain import ServerType, ServerTypeLocation
__all__ = [
"BoundServerType",
"ServerTypesPageResult",
"ServerTypesClient",
]
class BoundServerType(BoundModelBase, ServerType):
class BoundServerType(BoundModelBase[ServerType], ServerType):
_client: ServerTypesClient
model = ServerType
@ -15,7 +21,7 @@ class BoundServerType(BoundModelBase, ServerType):
def __init__(
self,
client: ServerTypesClient,
data: dict,
data: dict[str, Any],
complete: bool = True,
):
raw = data.get("locations")

View file

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

View file

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

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import warnings
from datetime import datetime
from typing import TYPE_CHECKING, Any, NamedTuple
@ -8,13 +9,21 @@ try:
except ImportError:
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 ..datacenters import BoundDatacenter
from ..firewalls import BoundFirewall
from ..floating_ips import BoundFloatingIP
from ..images import BoundImage, CreateImageResponse
from ..isos import BoundIso
from ..locations import BoundLocation, Location
from ..metrics import Metrics
from ..placement_groups import BoundPlacementGroup
from ..primary_ips import BoundPrimaryIP
@ -42,7 +51,6 @@ if TYPE_CHECKING:
from ..firewalls import Firewall
from ..images import Image
from ..isos import Iso
from ..locations import BoundLocation, Location
from ..networks import BoundNetwork, Network
from ..placement_groups import PlacementGroup
from ..server_types import ServerType
@ -51,16 +59,32 @@ if TYPE_CHECKING:
from .domain import ServerCreatePublicNetwork
class BoundServer(BoundModelBase, Server):
__all__ = [
"BoundServer",
"ServersPageResult",
"ServersClient",
]
class BoundServer(BoundModelBase[Server], Server):
_client: ServersClient
model = Server
# pylint: disable=too-many-locals
def __init__(self, client: ServersClient, data: dict, complete: bool = True):
datacenter = data.get("datacenter")
if datacenter is not None:
data["datacenter"] = BoundDatacenter(client._parent.datacenters, datacenter)
def __init__(
self,
client: ServersClient,
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", [])
if volumes:
@ -169,22 +193,18 @@ class BoundServer(BoundModelBase, Server):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a server.
"""
Returns a paginated list of Actions for a 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>`)
: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,
@ -196,16 +216,14 @@ class BoundServer(BoundModelBase, Server):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a server.
"""
Returns all Actions for a 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
: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)
@ -506,7 +524,10 @@ class ServersPageResult(NamedTuple):
meta: Meta
class ServersClient(ResourceClientBase):
class ServersClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/servers"
actions: ResourceActionsClient
@ -660,6 +681,13 @@ class ServersClient(ResourceClientBase):
if location is not None:
data["location"] = location.id_or_name
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
if ssh_keys is not None:
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(
self,
server: Server | BoundServer,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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
Returns a paginated list of Actions for a Server.
response = self._client.request(
url=f"{self._base_url}/{server.id}/actions",
method="GET",
params=params,
:param server: Server 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}/{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(
self,
server: Server | BoundServer,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param server: Server 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,

View file

@ -1,11 +1,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Literal
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
import warnings
from typing import TYPE_CHECKING, Literal, TypedDict
from ..core import BaseDomain, DomainIdentityMixin
@ -16,15 +12,36 @@ if TYPE_CHECKING:
from ..floating_ips import BoundFloatingIP
from ..images import BoundImage
from ..isos import BoundIso
from ..locations import BoundLocation
from ..metrics import Metrics
from ..networks import BoundNetwork, Network
from ..placement_groups import BoundPlacementGroup
from ..primary_ips import BoundPrimaryIP, PrimaryIP
from ..rdns import DNSPtr
from ..server_types import BoundServerType
from ..volumes import BoundVolume
from .client import BoundServer
__all__ = [
"Server",
"ServerProtection",
"CreateServerResponse",
"ResetPasswordResponse",
"EnableRescueResponse",
"RequestConsoleResponse",
"RebuildResponse",
"PublicNetwork",
"PublicNetworkFirewall",
"IPv4Address",
"IPv6Network",
"PrivateNet",
"ServerCreatePublicNetwork",
"GetMetricsResponse",
"MetricsType",
]
class Server(BaseDomain, DomainIdentityMixin):
"""Server Domain
@ -40,6 +57,12 @@ class Server(BaseDomain, DomainIdentityMixin):
Public network information.
:param server_type: :class:`BoundServerType <hcloud.server_types.client.BoundServerType>`
: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 iso: :class:`BoundIso <hcloud.isos.client.BoundIso>`, None
:param rescue_enabled: bool
@ -85,13 +108,13 @@ class Server(BaseDomain, DomainIdentityMixin):
STATUS_UNKNOWN = "unknown"
"""Server Status unknown"""
__api_properties__ = (
__properties__ = (
"id",
"name",
"status",
"public_net",
"server_type",
"datacenter",
"location",
"image",
"iso",
"rescue_enabled",
@ -108,7 +131,14 @@ class Server(BaseDomain, DomainIdentityMixin):
"primary_disk_size",
"placement_group",
)
__slots__ = __api_properties__
__api_properties__ = (
*__properties__,
"datacenter",
)
__slots__ = (
*__properties__,
"_datacenter",
)
# pylint: disable=too-many-locals
def __init__(
@ -120,6 +150,7 @@ class Server(BaseDomain, DomainIdentityMixin):
public_net: PublicNetwork | None = None,
server_type: BoundServerType | None = None,
datacenter: BoundDatacenter | None = None,
location: BoundLocation | None = None,
image: BoundImage | None = None,
iso: BoundIso | None = None,
rescue_enabled: bool | None = None,
@ -128,7 +159,7 @@ class Server(BaseDomain, DomainIdentityMixin):
outgoing_traffic: int | None = None,
ingoing_traffic: int | None = None,
included_traffic: int | None = None,
protection: dict | None = None,
protection: ServerProtection | None = None,
labels: dict[str, str] | None = None,
volumes: list[BoundVolume] | None = None,
private_net: list[PrivateNet] | None = None,
@ -138,10 +169,11 @@ class Server(BaseDomain, DomainIdentityMixin):
self.id = id
self.name = name
self.status = status
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
self.public_net = public_net
self.server_type = server_type
self.datacenter = datacenter
self.location = location
self.image = image
self.iso = iso
self.rescue_enabled = rescue_enabled
@ -167,6 +199,29 @@ class Server(BaseDomain, DomainIdentityMixin):
return o
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):
"""Create Server Response Domain
@ -392,7 +447,7 @@ class IPv6Network(BaseDomain):
self,
ip: str,
blocked: bool,
dns_ptr: list,
dns_ptr: list[DNSPtr],
):
self.ip = ip
self.blocked = blocked

View file

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

View file

@ -1,12 +1,11 @@
from __future__ import annotations
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin
__all__ = [
"SSHKey",
]
class SSHKey(BaseDomain, DomainIdentityMixin):
"""SSHKey Domain
@ -49,4 +48,4 @@ class SSHKey(BaseDomain, DomainIdentityMixin):
self.fingerprint = fingerprint
self.public_key = public_key
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:
from .._client import Client
__all__ = [
"BoundStorageBoxType",
"StorageBoxTypesPageResult",
"StorageBoxTypesClient",
]
class BoundStorageBoxType(BoundModelBase, StorageBoxType):
class BoundStorageBoxType(BoundModelBase[StorageBoxType], StorageBoxType):
_client: StorageBoxTypesClient
model = StorageBoxType

View file

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

View file

@ -2,7 +2,14 @@ from __future__ import annotations
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 ..locations import BoundLocation, Location
from ..ssh_keys import BoundSSHKey, SSHKey
@ -28,8 +35,18 @@ from .domain import (
if TYPE_CHECKING:
from .._client import Client
__all__ = [
"BoundStorageBox",
"BoundStorageBoxSnapshot",
"BoundStorageBoxSubaccount",
"StorageBoxesPageResult",
"StorageBoxSnapshotsPageResult",
"StorageBoxSubaccountsPageResult",
"StorageBoxesClient",
]
class BoundStorageBox(BoundModelBase, StorageBox):
class BoundStorageBox(BoundModelBase[StorageBox], StorageBox):
_client: StorageBoxesClient
model = StorageBox
@ -67,20 +84,20 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_actions_list(
self,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
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 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
: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 page: Page number to return.
:param per_page: Maximum number of entries returned per page.
: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.
Experimental:
Storage Box support is experimental, breaking changes may occur within minor releases.
@ -96,8 +113,8 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_actions(
self,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
Returns all Actions for a Storage Box.
@ -322,7 +339,7 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_snapshot_by_name(
self,
name: str,
) -> BoundStorageBoxSnapshot:
) -> BoundStorageBoxSnapshot | None:
"""
Returns a single Snapshot from a Storage Box.
@ -438,7 +455,7 @@ class BoundStorageBox(BoundModelBase, StorageBox):
def get_subaccount_by_username(
self,
username: str,
) -> BoundStorageBoxSubaccount:
) -> BoundStorageBoxSubaccount | None:
"""
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
model = StorageBoxSnapshot
@ -561,6 +578,8 @@ class BoundStorageBoxSnapshot(BoundModelBase, StorageBoxSnapshot):
super().__init__(client, data, complete)
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(
self.data_model.storage_box,
self.data_model.id,
@ -603,7 +622,9 @@ class BoundStorageBoxSnapshot(BoundModelBase, StorageBoxSnapshot):
return self._client.delete_snapshot(self)
class BoundStorageBoxSubaccount(BoundModelBase, StorageBoxSubaccount):
class BoundStorageBoxSubaccount(
BoundModelBase[StorageBoxSubaccount], StorageBoxSubaccount
):
_client: StorageBoxesClient
model = StorageBoxSubaccount
@ -627,6 +648,8 @@ class BoundStorageBoxSubaccount(BoundModelBase, StorageBoxSubaccount):
super().__init__(client, data, complete)
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(
self.data_model.storage_box,
self.data_model.id,
@ -737,7 +760,10 @@ class StorageBoxSubaccountsPageResult(NamedTuple):
meta: Meta
class StorageBoxesClient(ResourceClientBase):
class StorageBoxesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
"""
A client for the Storage Boxes API.
@ -1006,58 +1032,46 @@ class StorageBoxesClient(ResourceClientBase):
self,
storage_box: StorageBox | BoundStorageBox,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
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 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
:param storage_box: Storage Box to fetch the Actions from.
: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 page: Page number to return.
:param per_page: Maximum number of entries returned per page.
:param storage_box: Storage Box 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.
Experimental:
Storage Box support is experimental, breaking changes may occur within minor releases.
"""
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"/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),
return self._get_actions_list(
f"{self._base_url}/{storage_box.id}",
status=status,
sort=sort,
page=page,
per_page=per_page,
)
def get_actions(
self,
storage_box: StorageBox | BoundStorageBox,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
Returns all 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 sort: Sort resources by field and direction.
@ -1279,7 +1293,7 @@ class StorageBoxesClient(ResourceClientBase):
self,
storage_box: StorageBox | BoundStorageBox,
name: str,
) -> BoundStorageBoxSnapshot:
) -> BoundStorageBoxSnapshot | None:
"""
Returns a single Snapshot from a Storage Box.
@ -1500,7 +1514,7 @@ class StorageBoxesClient(ResourceClientBase):
self,
storage_box: StorageBox | BoundStorageBox,
username: str,
) -> BoundStorageBoxSubaccount:
) -> BoundStorageBoxSubaccount | None:
"""
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
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..actions import BoundAction
from ..core import BaseDomain, DomainIdentityMixin
from ..locations import BoundLocation, Location
@ -19,6 +14,26 @@ if TYPE_CHECKING:
BoundStorageBoxSubaccount,
)
__all__ = [
"StorageBox",
"StorageBoxAccessSettings",
"StorageBoxStats",
"StorageBoxSnapshotPlan",
"CreateStorageBoxResponse",
"DeleteStorageBoxResponse",
"StorageBoxFoldersResponse",
"StorageBoxSnapshot",
"StorageBoxSnapshotStats",
"CreateStorageBoxSnapshotResponse",
"DeleteStorageBoxSnapshotResponse",
"StorageBoxSubaccount",
"StorageBoxSubaccountAccessSettings",
"CreateStorageBoxSubaccountResponse",
"DeleteStorageBoxSubaccountResponse",
"StorageBoxStatus",
]
StorageBoxStatus = Literal[
"active",
"initializing",
@ -85,7 +100,7 @@ class StorageBox(BaseDomain, DomainIdentityMixin):
self.access_settings = access_settings
self.stats = stats
self.status = status
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
class StorageBoxAccessSettings(BaseDomain):
@ -288,7 +303,7 @@ class StorageBoxSnapshot(BaseDomain, DomainIdentityMixin):
self.is_automatic = is_automatic
self.labels = labels
self.storage_box = storage_box
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
self.stats = stats
@ -389,7 +404,7 @@ class StorageBoxSubaccount(BaseDomain, DomainIdentityMixin):
self.access_settings = access_settings
self.labels = labels
self.storage_box = storage_box
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
class StorageBoxSubaccountAccessSettings(BaseDomain):

View file

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

View file

@ -2,7 +2,14 @@ from __future__ import annotations
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 ..locations import BoundLocation
from .domain import CreateVolumeResponse, Volume
@ -13,12 +20,24 @@ if TYPE_CHECKING:
from ..servers import BoundServer, Server
class BoundVolume(BoundModelBase, Volume):
__all__ = [
"BoundVolume",
"VolumesPageResult",
"VolumesClient",
]
class BoundVolume(BoundModelBase[Volume], Volume):
_client: VolumesClient
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")
if location is not None:
data["location"] = BoundLocation(client._parent.locations, location)
@ -35,22 +54,18 @@ class BoundVolume(BoundModelBase, Volume):
def get_actions_list(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> ActionsPageResult:
"""Returns all action objects for a volume.
"""
Returns a paginated list of Actions for a 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>`)
: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
@ -58,16 +73,14 @@ class BoundVolume(BoundModelBase, Volume):
def get_actions(
self,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""Returns all action objects for a volume.
"""
Returns all Actions for a 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
: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)
@ -137,7 +150,10 @@ class VolumesPageResult(NamedTuple):
meta: Meta
class VolumesClient(ResourceClientBase):
class VolumesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
_base_url = "/volumes"
actions: ResourceActionsClient
@ -288,59 +304,40 @@ class VolumesClient(ResourceClientBase):
def get_actions_list(
self,
volume: Volume | BoundVolume,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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] = {}
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
Returns a paginated list of Actions for a Volume.
response = self._client.request(
url=f"{self._base_url}/{volume.id}/actions",
method="GET",
params=params,
:param volume: Volume 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}/{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(
self,
volume: Volume | BoundVolume,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> 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 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`
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
:param volume: Volume 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,

View file

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

View file

@ -3,16 +3,26 @@ from __future__ import annotations
from .client import (
BoundZone,
BoundZoneRRSet,
ZoneRRSetsPageResult,
ZonesClient,
ZonesPageResult,
)
from .domain import (
CreateZoneResponse,
CreateZoneRRSetResponse,
DeleteZoneResponse,
DeleteZoneRRSetResponse,
ExportZonefileResponse,
Zone,
ZoneAuthoritativeNameservers,
ZoneMode,
ZonePrimaryNameserver,
ZoneProtection,
ZoneRecord,
ZoneRegistrar,
ZoneRRSet,
ZoneRRSetProtection,
ZoneStatus,
)
__all__ = [
@ -26,4 +36,14 @@ __all__ = [
"ZoneRRSet",
"ZonesClient",
"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 ..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 .domain import (
CreateZoneResponse,
@ -22,8 +29,16 @@ from .domain import (
if TYPE_CHECKING:
from .._client import Client
__all__ = [
"BoundZone",
"BoundZoneRRSet",
"ZonesPageResult",
"ZoneRRSetsPageResult",
"ZonesClient",
]
class BoundZone(BoundModelBase, Zone):
class BoundZone(BoundModelBase[Zone], Zone):
_client: ZonesClient
model = Zone
@ -81,20 +96,20 @@ class BoundZone(BoundModelBase, Zone):
def get_actions_list(
self,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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
: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 page: Page number to return.
:param per_page: Maximum number of entries returned per page.
: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,
@ -107,16 +122,16 @@ class BoundZone(BoundModelBase, Zone):
def get_actions(
self,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
Returns all Actions for the Zone.
Returns all Actions for a Zone.
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 sort: Sort resources by field and direction.
:param status: Filter the Actions by status.
:param sort: Sort Actions by field and direction.
"""
return self._client.get_actions(
self,
@ -367,7 +382,7 @@ class BoundZone(BoundModelBase, Zone):
"""
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 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)
class BoundZoneRRSet(BoundModelBase, ZoneRRSet):
class BoundZoneRRSet(BoundModelBase[ZoneRRSet], ZoneRRSet):
_client: ZonesClient
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")
if raw is not None:
data["zone"] = BoundZone(client, data={"id": raw}, complete=False)
@ -422,6 +442,8 @@ class BoundZoneRRSet(BoundModelBase, ZoneRRSet):
super().__init__(client, data, complete)
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(
self.data_model.zone,
self.data_model.name,
@ -501,7 +523,7 @@ class BoundZoneRRSet(BoundModelBase, 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.
"""
@ -544,7 +566,10 @@ class ZoneRRSetsPageResult(NamedTuple):
meta: Meta
class ZonesClient(ResourceClientBase):
class ZonesClient(
ResourceClientBaseActionsMixin,
ResourceClientBase,
):
"""
ZoneClient is a client for the Zone (DNS) API.
@ -767,57 +792,45 @@ class ZonesClient(ResourceClientBase):
self,
zone: Zone | BoundZone,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
page: int | None = None,
per_page: int | None = None,
) -> 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
:param zone: Zone to fetch the Actions from.
: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 page: Page number to return.
:param per_page: Maximum number of entries returned per page.
:param zone: Zone 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.
"""
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"{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),
return self._get_actions_list(
f"{self._base_url}/{zone.id_or_name}",
status=status,
sort=sort,
page=page,
per_page=per_page,
)
def get_actions(
self,
zone: Zone | BoundZone,
*,
status: list[str] | None = None,
sort: list[str] | None = None,
status: list[ActionStatus] | None = None,
sort: list[ActionSort] | None = None,
) -> list[BoundAction]:
"""
Returns all Actions for a Zone.
See https://docs.hetzner.cloud/reference/cloud#zones-list-zones
:param zone: Zone to fetch the Actions from.
: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 zone: Zone 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,
@ -1208,7 +1221,7 @@ class ZonesClient(ResourceClientBase):
"""
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 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
try:
from dateutil.parser import isoparse
except ImportError:
isoparse = None
from ..core import BaseDomain, DomainIdentityMixin
if TYPE_CHECKING:
@ -14,6 +9,24 @@ if TYPE_CHECKING:
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"]
ZoneStatus = Literal["ok", "updating", "error"]
ZoneRegistrar = Literal["hetzner", "other", "unknown"]
@ -81,7 +94,7 @@ class Zone(BaseDomain, DomainIdentityMixin):
):
self.id = id
self.name = name
self.created = isoparse(created) if created else None
self.created = self._parse_datetime(created)
self.mode = mode
self.ttl = ttl
self.labels = labels
@ -188,11 +201,7 @@ class ZoneAuthoritativeNameservers(BaseDomain):
):
self.assigned = assigned
self.delegated = delegated
self.delegation_last_check = (
isoparse(delegation_last_check)
if delegation_last_check is not None
else None
)
self.delegation_last_check = self._parse_datetime(delegation_last_check)
self.delegation_status = delegation_status