Skip to content

File tree

5 files changed

+641
-35
lines changed

5 files changed

+641
-35
lines changed

hcloud/storage_boxes/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,34 @@
22

33
from .client import (
44
BoundStorageBox,
5+
BoundStorageBoxSnapshot,
56
StorageBoxesClient,
67
StorageBoxesPageResult,
8+
StorageBoxSnapshotsPageResult,
79
)
810
from .domain import (
911
CreateStorageBoxResponse,
1012
DeleteStorageBoxResponse,
1113
StorageBox,
1214
StorageBoxAccessSettings,
1315
StorageBoxFoldersResponse,
16+
StorageBoxSnapshot,
1417
StorageBoxSnapshotPlan,
1518
StorageBoxStats,
1619
)
1720

1821
__all__ = [
1922
"BoundStorageBox",
23+
"BoundStorageBoxSnapshot",
2024
"CreateStorageBoxResponse",
2125
"DeleteStorageBoxResponse",
2226
"StorageBox",
2327
"StorageBoxAccessSettings",
2428
"StorageBoxesClient",
2529
"StorageBoxesPageResult",
2630
"StorageBoxFoldersResponse",
31+
"StorageBoxSnapshot",
2732
"StorageBoxSnapshotPlan",
33+
"StorageBoxSnapshotsPageResult",
2834
"StorageBoxStats",
2935
]

hcloud/storage_boxes/client.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
from ..storage_box_types import BoundStorageBoxType, StorageBoxType
99
from .domain import (
1010
CreateStorageBoxResponse,
11+
CreateStorageBoxSnapshotResponse,
1112
DeleteStorageBoxResponse,
13+
DeleteStorageBoxSnapshotResponse,
1214
StorageBox,
1315
StorageBoxAccessSettings,
1416
StorageBoxFoldersResponse,
1517
StorageBoxSnapshot,
1618
StorageBoxSnapshotPlan,
19+
StorageBoxSnapshotStats,
1720
StorageBoxStats,
1821
)
1922

@@ -105,11 +108,42 @@ def get_actions(
105108
# TODO: implement bound methods
106109

107110

111+
class BoundStorageBoxSnapshot(BoundModelBase, StorageBoxSnapshot):
112+
_client: StorageBoxesClient
113+
114+
model = StorageBoxSnapshot
115+
116+
def __init__(
117+
self,
118+
client: StorageBoxesClient,
119+
data: dict[str, Any],
120+
complete: bool = True,
121+
):
122+
raw = data.get("storage_box")
123+
if raw is not None:
124+
data["storage_box"] = BoundStorageBox(
125+
client, data={"id": raw}, complete=False
126+
)
127+
128+
raw = data.get("stats")
129+
if raw is not None:
130+
data["stats"] = StorageBoxSnapshotStats.from_dict(raw)
131+
132+
super().__init__(client, data, complete)
133+
134+
# TODO: implement bound methods
135+
136+
108137
class StorageBoxesPageResult(NamedTuple):
109138
storage_boxes: list[BoundStorageBox]
110139
meta: Meta
111140

112141

142+
class StorageBoxSnapshotsPageResult(NamedTuple):
143+
snapshots: list[BoundStorageBoxSnapshot]
144+
meta: Meta
145+
146+
113147
class StorageBoxesClient(ResourceClientBase):
114148
"""
115149
A client for the Storage Boxes API.
@@ -556,3 +590,198 @@ def enable_snapshot_plan(
556590
json=data,
557591
)
558592
return BoundAction(self._parent.actions, response["action"])
593+
594+
# Snapshots
595+
###########################################################################
596+
597+
def get_snapshot_by_id(
598+
self,
599+
storage_box: StorageBox | BoundStorageBox,
600+
id: int,
601+
) -> BoundStorageBoxSnapshot:
602+
"""
603+
Returns a single Snapshot from a Storage Box.
604+
605+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-get-a-snapshot
606+
607+
:param storage_box: Storage Box to get the Snapshot from.
608+
:param id: ID of the Snapshot.
609+
"""
610+
response = self._client.request(
611+
method="GET",
612+
url=f"{self._base_url}/{storage_box.id}/snapshots/{id}",
613+
)
614+
return BoundStorageBoxSnapshot(self, response["snapshot"])
615+
616+
def get_snapshot_by_name(
617+
self,
618+
storage_box: StorageBox | BoundStorageBox,
619+
name: str,
620+
) -> BoundStorageBoxSnapshot:
621+
"""
622+
Returns a single Snapshot from a Storage Box.
623+
624+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-list-snapshots
625+
626+
:param storage_box: Storage Box to get the Snapshot from.
627+
:param name: Name of the Snapshot.
628+
"""
629+
return self._get_first_by(self.get_snapshot_list, storage_box, name=name)
630+
631+
def get_snapshot_list(
632+
self,
633+
storage_box: StorageBox | BoundStorageBox,
634+
*,
635+
name: str | None = None,
636+
is_automatic: bool | None = None,
637+
label_selector: str | None = None,
638+
sort: list[str] | None = None,
639+
) -> StorageBoxSnapshotsPageResult:
640+
"""
641+
Returns all Snapshots for a Storage Box.
642+
643+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-list-snapshots
644+
645+
:param storage_box: Storage Box to get the Snapshots from.
646+
:param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
647+
:param is_automatic: Filter wether the snapshot was made by a Snapshot Plan.
648+
:param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
649+
:param sort: Sort resources by field and direction.
650+
"""
651+
params: dict[str, Any] = {}
652+
if name is not None:
653+
params["name"] = name
654+
if is_automatic is not None:
655+
params["is_automatic"] = is_automatic
656+
if label_selector is not None:
657+
params["label_selector"] = label_selector
658+
if sort is not None:
659+
params["sort"] = sort
660+
661+
response = self._client.request(
662+
method="GET",
663+
url=f"{self._base_url}/{storage_box.id}/snapshots",
664+
params=params,
665+
)
666+
return StorageBoxSnapshotsPageResult(
667+
snapshots=[
668+
BoundStorageBoxSnapshot(self, item) for item in response["snapshots"]
669+
],
670+
meta=Meta.parse_meta(response),
671+
)
672+
673+
def get_snapshot_all(
674+
self,
675+
storage_box: StorageBox | BoundStorageBox,
676+
*,
677+
name: str | None = None,
678+
is_automatic: bool | None = None,
679+
label_selector: str | None = None,
680+
sort: list[str] | None = None,
681+
) -> list[BoundStorageBoxSnapshot]:
682+
"""
683+
Returns all Snapshots for a Storage Box.
684+
685+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-list-snapshots
686+
687+
:param storage_box: Storage Box to get the Snapshots from.
688+
:param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
689+
:param is_automatic: Filter wether the snapshot was made by a Snapshot Plan.
690+
:param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
691+
:param sort: Sort resources by field and direction.
692+
"""
693+
# The endpoint does not have pagination, forward to the list method.
694+
result, _ = self.get_snapshot_list(
695+
storage_box,
696+
name=name,
697+
is_automatic=is_automatic,
698+
label_selector=label_selector,
699+
sort=sort,
700+
)
701+
return result
702+
703+
def create_snapshot(
704+
self,
705+
storage_box: StorageBox | BoundStorageBox,
706+
*,
707+
description: str | None = None,
708+
labels: dict[str, str] | None = None,
709+
) -> CreateStorageBoxSnapshotResponse:
710+
"""
711+
Creates a Snapshot of the Storage Box.
712+
713+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-create-a-snapshot
714+
715+
:param storage_box: Storage Box to create a Snapshot from.
716+
:param description: Description of the Snapshot.
717+
:param labels: User-defined labels (key/value pairs) for the Resource.
718+
"""
719+
data: dict[str, Any] = {}
720+
if description is not None:
721+
data["description"] = description
722+
if labels is not None:
723+
data["labels"] = labels
724+
725+
response = self._client.request(
726+
method="POST",
727+
url=f"{self._base_url}/{storage_box.id}/snapshots",
728+
json=data,
729+
)
730+
return CreateStorageBoxSnapshotResponse(
731+
snapshot=BoundStorageBoxSnapshot(self, response["snapshot"]),
732+
action=BoundAction(self._parent.actions, response["action"]),
733+
)
734+
735+
def update_snapshot(
736+
self,
737+
snapshot: StorageBoxSnapshot | BoundStorageBoxSnapshot,
738+
*,
739+
description: str | None = None,
740+
labels: dict[str, str] | None = None,
741+
) -> BoundStorageBoxSnapshot:
742+
"""
743+
Updates a Storage Box Snapshot.
744+
745+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-update-a-snapshot
746+
747+
:param snapshot: Storage Box Snapshot to update.
748+
:param description: Description of the Snapshot.
749+
:param labels: User-defined labels (key/value pairs) for the Resource.
750+
"""
751+
if snapshot.storage_box is None:
752+
raise ValueError("snapshot storage_box property is none")
753+
754+
data: dict[str, Any] = {}
755+
if description is not None:
756+
data["description"] = description
757+
if labels is not None:
758+
data["labels"] = labels
759+
760+
response = self._client.request(
761+
method="PUT",
762+
url=f"{self._base_url}/{snapshot.storage_box.id}/snapshots/{snapshot.id}",
763+
json=data,
764+
)
765+
return BoundStorageBoxSnapshot(self, response["snapshot"])
766+
767+
def delete_snapshot(
768+
self,
769+
snapshot: StorageBoxSnapshot | BoundStorageBoxSnapshot,
770+
) -> DeleteStorageBoxSnapshotResponse:
771+
"""
772+
Deletes a Storage Box Snapshot.
773+
774+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-delete-a-snapshot
775+
776+
:param snapshot: Storage Box Snapshot to delete.
777+
"""
778+
if snapshot.storage_box is None:
779+
raise ValueError("snapshot storage_box property is none")
780+
781+
response = self._client.request(
782+
method="DELETE",
783+
url=f"{self._base_url}/{snapshot.storage_box.id}/snapshots/{snapshot.id}",
784+
)
785+
return DeleteStorageBoxSnapshotResponse(
786+
action=BoundAction(self._parent.actions, response["action"]),
787+
)

hcloud/storage_boxes/domain.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..storage_box_types import BoundStorageBoxType, StorageBoxType
1111

1212
if TYPE_CHECKING:
13-
from .client import BoundStorageBox
13+
from .client import BoundStorageBox, BoundStorageBoxSnapshot
1414

1515
StorageBoxStatus = Literal[
1616
"active",
@@ -252,17 +252,89 @@ class StorageBoxSnapshot(BaseDomain, DomainIdentityMixin):
252252
Storage Box Snapshot Domain.
253253
"""
254254

255-
# TODO: full domain
256255
__api_properties__ = (
257256
"id",
258257
"name",
258+
"description",
259+
"is_automatic",
260+
"labels",
261+
"storage_box",
262+
"created",
263+
"stats",
259264
)
260265
__slots__ = __api_properties__
261266

262267
def __init__(
263268
self,
264269
id: int | None = None,
265270
name: str | None = None,
271+
description: str | None = None,
272+
is_automatic: bool | None = None,
273+
labels: dict[str, str] | None = None,
274+
storage_box: BoundStorageBox | StorageBox | None = None,
275+
created: str | None = None,
276+
stats: StorageBoxSnapshotStats | None = None,
266277
):
267278
self.id = id
268279
self.name = name
280+
self.description = description
281+
self.is_automatic = is_automatic
282+
self.labels = labels
283+
self.storage_box = storage_box
284+
self.created = isoparse(created) if created else None
285+
self.stats = stats
286+
287+
288+
class StorageBoxSnapshotStats(BaseDomain):
289+
"""
290+
Storage Box Snapshot Stats Domain.
291+
"""
292+
293+
__api_properties__ = (
294+
"size",
295+
"size_filesystem",
296+
)
297+
__slots__ = __api_properties__
298+
299+
def __init__(
300+
self,
301+
size: int,
302+
size_filesystem: int,
303+
):
304+
self.size = size
305+
self.size_filesystem = size_filesystem
306+
307+
308+
class CreateStorageBoxSnapshotResponse(BaseDomain):
309+
"""
310+
Create Storage Box Snapshot Response Domain.
311+
"""
312+
313+
__api_properties__ = (
314+
"snapshot",
315+
"action",
316+
)
317+
__slots__ = __api_properties__
318+
319+
def __init__(
320+
self,
321+
snapshot: BoundStorageBoxSnapshot,
322+
action: BoundAction,
323+
):
324+
self.snapshot = snapshot
325+
self.action = action
326+
327+
328+
class DeleteStorageBoxSnapshotResponse(BaseDomain):
329+
"""
330+
Delete Storage Box Snapshot Response Domain.
331+
"""
332+
333+
__api_properties__ = ("action",)
334+
__slots__ = __api_properties__
335+
336+
def __init__(
337+
self,
338+
action: BoundAction,
339+
):
340+
self.action = action

0 commit comments

Comments
 (0)