Skip to content

Commit a3f3ab1

Browse files
committed
feat: implementation
1 parent bec8577 commit a3f3ab1

File tree

5 files changed

+645
-35
lines changed

5 files changed

+645
-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: 233 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,202 @@ 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/cloud#zone-snapshots-list-snapshots
625+
626+
:param storage_box: Storage Box to get the Snapshot from.
627+
:param id: 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/cloud#zone-snapshots-list-snapshots
644+
645+
:param zone: Zone to fetch the RRSets from.
646+
:param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
647+
:param type: Filter resources by their type. The response will only contain the resources matching exactly the specified type.
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+
:param page: Page number to return.
651+
:param per_page: Maximum number of entries returned per page.
652+
"""
653+
params: dict[str, Any] = {}
654+
if name is not None:
655+
params["name"] = name
656+
if is_automatic is not None:
657+
params["is_automatic"] = is_automatic
658+
if label_selector is not None:
659+
params["label_selector"] = label_selector
660+
if sort is not None:
661+
params["sort"] = sort
662+
663+
response = self._client.request(
664+
method="GET",
665+
url=f"{self._base_url}/{storage_box.id}/snapshots",
666+
params=params,
667+
)
668+
return StorageBoxSnapshotsPageResult(
669+
snapshots=[
670+
BoundStorageBoxSnapshot(self, item) for item in response["snapshots"]
671+
],
672+
meta=Meta.parse_meta(response),
673+
)
674+
675+
def get_snapshot_all(
676+
self,
677+
storage_box: StorageBox | BoundStorageBox,
678+
*,
679+
name: str | None = None,
680+
is_automatic: bool | None = None,
681+
label_selector: str | None = None,
682+
sort: list[str] | None = None,
683+
) -> list[BoundStorageBoxSnapshot]:
684+
"""
685+
Returns all Snapshots for a Storage Box.
686+
687+
See https://docs.hetzner.cloud/reference/cloud#zone-snapshots-list-snapshots
688+
689+
:param zone: Zone to fetch the RRSets from.
690+
:param name: Filter resources by their name. The response will only contain the resources matching exactly the specified name.
691+
:param type: Filter resources by their type. The response will only contain the resources matching exactly the specified type.
692+
:param label_selector: Filter resources by labels. The response will only contain resources matching the label selector.
693+
:param sort: Sort resources by field and direction.
694+
"""
695+
# The endpoint does not have pagination, forward to the list method.
696+
result, _ = self.get_snapshot_list(
697+
storage_box,
698+
name=name,
699+
is_automatic=is_automatic,
700+
label_selector=label_selector,
701+
sort=sort,
702+
)
703+
return result
704+
705+
def create_snapshot(
706+
self,
707+
storage_box: StorageBox | BoundStorageBox,
708+
*,
709+
description: str | None = None,
710+
labels: dict[str, str] | None = None,
711+
) -> CreateStorageBoxSnapshotResponse:
712+
"""
713+
Creates a Snapshot of the Storage Box.
714+
715+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-create-a-snapshot
716+
717+
:param zone: Zone to create the RRSets in.
718+
:param name: Name of the RRSet.
719+
:param type: Type of the RRSet.
720+
:param ttl: Time To Live (TTL) of the RRSet.
721+
:param labels: User-defined labels (key/value pairs) for the Resource.
722+
:param records: Records of the RRSet.
723+
"""
724+
data: dict[str, Any] = {}
725+
if description is not None:
726+
data["description"] = description
727+
if labels is not None:
728+
data["labels"] = labels
729+
730+
response = self._client.request(
731+
method="POST",
732+
url=f"{self._base_url}/{storage_box.id}/snapshots",
733+
json=data,
734+
)
735+
return CreateStorageBoxSnapshotResponse(
736+
snapshot=BoundStorageBoxSnapshot(self, response["snapshot"]),
737+
action=BoundAction(self._parent.actions, response["action"]),
738+
)
739+
740+
def update_snapshot(
741+
self,
742+
snapshot: StorageBoxSnapshot | BoundStorageBoxSnapshot,
743+
*,
744+
description: str | None = None,
745+
labels: dict[str, str] | None = None,
746+
) -> BoundStorageBoxSnapshot:
747+
"""
748+
Updates a Storage Box Snapshot.
749+
750+
See https://docs.hetzner.cloud/reference/hetzner#storage-box-snapshots-update-a-snapshot
751+
752+
:param snapshot: RRSet to update.
753+
:param labels: User-defined labels (key/value pairs) for the Resource.
754+
"""
755+
if snapshot.storage_box is None:
756+
raise ValueError("snapshot storage_box property is none")
757+
758+
data: dict[str, Any] = {}
759+
if description is not None:
760+
data["description"] = description
761+
if labels is not None:
762+
data["labels"] = labels
763+
764+
response = self._client.request(
765+
method="PUT",
766+
url=f"{self._base_url}/{snapshot.storage_box.id}/snapshots/{snapshot.id}",
767+
json=data,
768+
)
769+
return BoundStorageBoxSnapshot(self, response["snapshot"])
770+
771+
def delete_snapshot(
772+
self,
773+
snapshot: StorageBoxSnapshot | BoundStorageBoxSnapshot,
774+
) -> DeleteStorageBoxSnapshotResponse:
775+
"""
776+
Deletes a ZoneRRSet.
777+
778+
See https://docs.hetzner.cloud/reference/cloud#zone-snapshots-delete-an-snapshot
779+
780+
:param snapshot: RRSet to delete.
781+
"""
782+
if snapshot.storage_box is None:
783+
raise ValueError("snapshot storage_box property is none")
784+
785+
response = self._client.request(
786+
method="DELETE",
787+
url=f"{self._base_url}/{snapshot.storage_box.id}/snapshots/{snapshot.id}",
788+
)
789+
return DeleteStorageBoxSnapshotResponse(
790+
action=BoundAction(self._parent.actions, response["action"]),
791+
)

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)