Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add to_dict method to domain classes #270

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions hcloud/core/domain.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from __future__ import annotations

from datetime import datetime
from typing import Any

from .client import BoundModelBase


class BaseDomain:
__slots__ = ()
__slots__: tuple[str, ...] = ()

@classmethod
def from_dict(cls, data: dict): # type: ignore[no-untyped-def]
Expand All @@ -12,11 +17,34 @@ def from_dict(cls, data: dict): # type: ignore[no-untyped-def]
supported_data = {k: v for k, v in data.items() if k in cls.__slots__}
return cls(**supported_data)

def to_dict(self) -> dict:
"""Recursively convert a domain object to a dict."""
return _make_serializable(self) # type: ignore

def __repr__(self) -> str:
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__slots__] # type: ignore[var-annotated]
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__slots__]
return f"{self.__class__.__qualname__}({', '.join(kwargs)})"


def _make_serializable(value: Any) -> dict | list | str | int | float:
if isinstance(value, (BaseDomain, BoundModelBase)):
if isinstance(value, BoundModelBase):
value = value.data_model

return {key: _make_serializable(getattr(value, key)) for key in value.__slots__}

if isinstance(value, dict):
return {key: _make_serializable(value[key]) for key in value}

if isinstance(value, list):
return [_make_serializable(child) for child in value]

if isinstance(value, datetime):
return value.isoformat()

return value


class DomainIdentityMixin:
__slots__ = ()

Expand Down
193 changes: 193 additions & 0 deletions tests/unit/core/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
from __future__ import annotations

import pytest


@pytest.fixture()
def server_dict():
return {
"id": 35555937,
"name": "tmp",
"status": "running",
"created": "2023-08-03T13:06:14+00:00",
"public_net": {
"ipv4": {
"ip": "95.217.11.207",
"blocked": False,
"dns_ptr": "static.207.11.217.95.clients.your-server.de",
"id": 36311011,
},
"ipv6": {
"ip": "2a01:4f9:c012:63c9::/64",
"blocked": False,
"dns_ptr": [],
"id": 36311012,
},
"floating_ips": [],
"firewalls": [],
},
"private_net": [],
"server_type": {
"id": 1,
"name": "cx11",
"description": "CX11",
"cores": 1,
"memory": 2.0,
"disk": 20,
"deprecated": False,
"prices": [
{
"location": "fsn1",
"price_hourly": {
"net": "0.0052000000",
"gross": "0.0061880000000000",
},
"price_monthly": {
"net": "3.2900000000",
"gross": "3.9151000000000000",
},
},
{
"location": "hel1",
"price_hourly": {
"net": "0.0052000000",
"gross": "0.0061880000000000",
},
"price_monthly": {
"net": "3.2900000000",
"gross": "3.9151000000000000",
},
},
{
"location": "nbg1",
"price_hourly": {
"net": "0.0052000000",
"gross": "0.0061880000000000",
},
"price_monthly": {
"net": "3.2900000000",
"gross": "3.9151000000000000",
},
},
],
"storage_type": "local",
"cpu_type": "shared",
"architecture": "x86",
"included_traffic": 21990232555520,
"deprecation": None,
},
"datacenter": {
"id": 3,
"name": "hel1-dc2",
"description": "Helsinki 1 virtual DC 2",
"location": {
"id": 3,
"name": "hel1",
"description": "Helsinki DC Park 1",
"country": "FI",
"city": "Helsinki",
"latitude": 60.169855,
"longitude": 24.938379,
"network_zone": "eu-central",
},
"server_types": {
"supported": [
1,
3,
5,
7,
9,
22,
23,
24,
25,
26,
33,
34,
35,
36,
37,
38,
45,
93,
94,
95,
],
"available": [
1,
3,
5,
7,
9,
22,
23,
24,
25,
26,
33,
34,
35,
36,
37,
38,
],
"available_for_migration": [
1,
3,
5,
7,
9,
22,
23,
24,
25,
26,
33,
34,
35,
36,
37,
38,
96,
97,
98,
99,
100,
101,
],
},
},
"image": {
"id": 114690387,
"type": "system",
"status": "available",
"name": "debian-12",
"description": "Debian 12",
"image_size": None,
"disk_size": 5,
"created": "2023-06-13T06:00:02+00:00",
"created_from": None,
"bound_to": None,
"os_flavor": "debian",
"os_version": "12",
"rapid_deploy": True,
"protection": {"delete": False},
"deprecated": None,
"labels": {},
"deleted": None,
"architecture": "x86",
},
"iso": None,
"rescue_enabled": False,
"locked": False,
"backup_window": None,
"outgoing_traffic": None,
"ingoing_traffic": None,
"included_traffic": 21990232555520,
"protection": {"delete": False, "rebuild": False},
"labels": {},
"volumes": [],
# "load_balancers": [],
"primary_disk_size": 20,
"placement_group": None,
}
38 changes: 38 additions & 0 deletions tests/unit/core/test_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,44 @@ def test_from_dict_ok(self, data_dict, expected_result):
for k, v in expected_result.items():
assert getattr(model, k) == v

@pytest.mark.parametrize(
"data,expected",
[
(
SomeOtherDomain(id=1, name="name1"),
{"id": 1, "name": "name1", "child": None},
),
(
SomeOtherDomain(
id=2, name="name2", child=SomeOtherDomain(id=3, name="name3")
),
{
"id": 2,
"name": "name2",
"child": {"id": 3, "name": "name3", "child": None},
},
),
(
SomeOtherDomain(
id=2, name="name2", child=[SomeOtherDomain(id=3, name="name3")]
),
{
"id": 2,
"name": "name2",
"child": [{"id": 3, "name": "name3", "child": None}],
},
),
],
)
def test_to_dict_ok(self, data, expected):
assert data.to_dict() == expected

def test_from_dict_and_to_dict_ok(self, server_dict: dict):
from hcloud.servers import Server

server = Server.from_dict(server_dict)
assert server.to_dict() == server_dict

@pytest.mark.parametrize(
"data,expected",
[
Expand Down