Skip to content

Commit 4e85c8c

Browse files
committed
feat: implementation
1 parent 44a81ca commit 4e85c8c

File tree

2 files changed

+296
-2
lines changed

2 files changed

+296
-2
lines changed

hcloud/storage_boxes/client.py

Lines changed: 222 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,41 @@ 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+
123+
raw = data.get("storage_box")
124+
if raw is not None:
125+
data["zone"] = BoundStorageBox(client, data={"id": raw}, complete=False)
126+
127+
raw = data.get("stats")
128+
if raw is not None:
129+
data["stats"] = StorageBoxSnapshotStats.from_dict(raw)
130+
131+
super().__init__(client, data, complete)
132+
133+
# TODO: implement bound methods
134+
135+
108136
class StorageBoxesPageResult(NamedTuple):
109137
storage_boxes: list[BoundStorageBox]
110138
meta: Meta
111139

112140

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

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)