Skip to content

Commit fecccdb

Browse files
Merge pull request #616 from linode/dev
v5.38.0
2 parents a6f0b01 + 06b09b8 commit fecccdb

File tree

8 files changed

+311
-2
lines changed

8 files changed

+311
-2
lines changed

linode_api4/objects/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ def _flatten_request_body_recursive(data: Any, is_put: bool = False) -> Any:
530530
if isinstance(data, Base):
531531
return data.id
532532

533+
if isinstance(data, ExplicitNullValue) or data == ExplicitNullValue:
534+
return None
535+
533536
if isinstance(data, MappedObject) or issubclass(type(data), JSONObject):
534537
return data._serialize(is_put=is_put)
535538

linode_api4/objects/lke.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ class LKENodePool(DerivedBase):
205205
# directly exposed in the node pool response.
206206
"k8s_version": Property(mutable=True),
207207
"update_strategy": Property(mutable=True),
208+
"firewall_id": Property(mutable=True),
208209
}
209210

210211
def _parse_raw_node(

test/fixtures/lke_clusters_18881_pools_456.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"bar": "foo"
3636
},
3737
"label": "example-node-pool",
38+
"firewall_id": 456,
3839
"type": "g6-standard-4",
3940
"disk_encryption": "enabled"
4041
}

test/fixtures/lke_clusters_18882_pools_789.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
"tags": [],
1616
"disk_encryption": "enabled",
1717
"k8s_version": "1.31.1+lke1",
18+
"firewall_id": 789,
1819
"update_strategy": "rolling_update"
1920
}

test/integration/models/lke/test_lke.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def lke_cluster_with_apl(test_linode_client):
138138

139139

140140
@pytest.fixture(scope="session")
141-
def lke_cluster_enterprise(test_linode_client):
141+
def lke_cluster_enterprise(e2e_test_firewall, test_linode_client):
142142
# We use the oldest version here so we can test upgrades
143143
version = sorted(
144144
v.id for v in test_linode_client.lke.tier("enterprise").versions()
@@ -153,6 +153,7 @@ def lke_cluster_enterprise(test_linode_client):
153153
3,
154154
k8s_version=version,
155155
update_strategy="rolling_update",
156+
firewall_id=e2e_test_firewall.id,
156157
)
157158
label = get_test_label() + "_cluster"
158159

@@ -434,13 +435,18 @@ def test_lke_cluster_with_apl(lke_cluster_with_apl):
434435
)
435436

436437

437-
def test_lke_cluster_enterprise(test_linode_client, lke_cluster_enterprise):
438+
def test_lke_cluster_enterprise(
439+
e2e_test_firewall,
440+
test_linode_client,
441+
lke_cluster_enterprise,
442+
):
438443
lke_cluster_enterprise.invalidate()
439444
assert lke_cluster_enterprise.tier == "enterprise"
440445

441446
pool = lke_cluster_enterprise.pools[0]
442447
assert str(pool.k8s_version) == lke_cluster_enterprise.k8s_version.id
443448
assert pool.update_strategy == "rolling_update"
449+
assert pool.firewall_id == e2e_test_firewall.id
444450

445451
target_version = sorted(
446452
v.id for v in test_linode_client.lke.tier("enterprise").versions()

test/integration/models/monitor/test_monitor.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ def test_get_supported_services(test_linode_client):
5555
assert isinstance(metric_definitions[0], MonitorMetricsDefinition)
5656

5757

58+
def test_get_not_supported_service(test_linode_client):
59+
client = test_linode_client
60+
with pytest.raises(RuntimeError) as err:
61+
client.load(MonitorService, "saas")
62+
assert "[404] Not found" in str(err.value)
63+
64+
5865
# Test Helpers
5966
def get_db_engine_id(client: LinodeClient, engine: str):
6067
engines = client.database.engines()

test/unit/objects/base_test.py

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
from dataclasses import dataclass
2+
from test.unit.base import ClientBaseCase
3+
4+
from linode_api4.objects import Base, JSONObject, MappedObject, Property
5+
from linode_api4.objects.base import (
6+
ExplicitNullValue,
7+
_flatten_request_body_recursive,
8+
)
9+
10+
11+
class FlattenRequestBodyRecursiveCase(ClientBaseCase):
12+
"""Test cases for _flatten_request_body_recursive function"""
13+
14+
def test_flatten_primitive_types(self):
15+
"""Test that primitive types are returned as-is"""
16+
self.assertEqual(_flatten_request_body_recursive(123), 123)
17+
self.assertEqual(_flatten_request_body_recursive("test"), "test")
18+
self.assertEqual(_flatten_request_body_recursive(3.14), 3.14)
19+
self.assertEqual(_flatten_request_body_recursive(True), True)
20+
self.assertEqual(_flatten_request_body_recursive(False), False)
21+
self.assertEqual(_flatten_request_body_recursive(None), None)
22+
23+
def test_flatten_dict(self):
24+
"""Test that dicts are recursively flattened"""
25+
test_dict = {"key1": "value1", "key2": 123, "key3": True}
26+
result = _flatten_request_body_recursive(test_dict)
27+
self.assertEqual(result, test_dict)
28+
29+
def test_flatten_nested_dict(self):
30+
"""Test that nested dicts are recursively flattened"""
31+
test_dict = {
32+
"level1": {
33+
"level2": {"level3": "value", "number": 42},
34+
"string": "test",
35+
},
36+
"array": [1, 2, 3],
37+
}
38+
result = _flatten_request_body_recursive(test_dict)
39+
self.assertEqual(result, test_dict)
40+
41+
def test_flatten_list(self):
42+
"""Test that lists are recursively flattened"""
43+
test_list = [1, "two", 3.0, True]
44+
result = _flatten_request_body_recursive(test_list)
45+
self.assertEqual(result, test_list)
46+
47+
def test_flatten_nested_list(self):
48+
"""Test that nested lists are recursively flattened"""
49+
test_list = [[1, 2], [3, [4, 5]], "string"]
50+
result = _flatten_request_body_recursive(test_list)
51+
self.assertEqual(result, test_list)
52+
53+
def test_flatten_base_object(self):
54+
"""Test that Base objects are flattened to their ID"""
55+
56+
class TestBase(Base):
57+
api_endpoint = "/test/{id}"
58+
properties = {
59+
"id": Property(identifier=True),
60+
"label": Property(mutable=True),
61+
}
62+
63+
obj = TestBase(self.client, 123)
64+
result = _flatten_request_body_recursive(obj)
65+
self.assertEqual(result, 123)
66+
67+
def test_flatten_base_object_in_dict(self):
68+
"""Test that Base objects in dicts are flattened to their ID"""
69+
70+
class TestBase(Base):
71+
api_endpoint = "/test/{id}"
72+
properties = {
73+
"id": Property(identifier=True),
74+
"label": Property(mutable=True),
75+
}
76+
77+
obj = TestBase(self.client, 456)
78+
test_dict = {"resource": obj, "name": "test"}
79+
result = _flatten_request_body_recursive(test_dict)
80+
self.assertEqual(result, {"resource": 456, "name": "test"})
81+
82+
def test_flatten_base_object_in_list(self):
83+
"""Test that Base objects in lists are flattened to their ID"""
84+
85+
class TestBase(Base):
86+
api_endpoint = "/test/{id}"
87+
properties = {
88+
"id": Property(identifier=True),
89+
"label": Property(mutable=True),
90+
}
91+
92+
obj1 = TestBase(self.client, 111)
93+
obj2 = TestBase(self.client, 222)
94+
test_list = [obj1, "middle", obj2]
95+
result = _flatten_request_body_recursive(test_list)
96+
self.assertEqual(result, [111, "middle", 222])
97+
98+
def test_flatten_explicit_null_instance(self):
99+
"""Test that ExplicitNullValue instances are converted to None"""
100+
result = _flatten_request_body_recursive(ExplicitNullValue())
101+
self.assertIsNone(result)
102+
103+
def test_flatten_explicit_null_class(self):
104+
"""Test that ExplicitNullValue class is converted to None"""
105+
result = _flatten_request_body_recursive(ExplicitNullValue)
106+
self.assertIsNone(result)
107+
108+
def test_flatten_explicit_null_in_dict(self):
109+
"""Test that ExplicitNullValue in dicts is converted to None"""
110+
test_dict = {
111+
"field1": "value",
112+
"field2": ExplicitNullValue(),
113+
"field3": ExplicitNullValue,
114+
}
115+
result = _flatten_request_body_recursive(test_dict)
116+
self.assertEqual(
117+
result, {"field1": "value", "field2": None, "field3": None}
118+
)
119+
120+
def test_flatten_explicit_null_in_list(self):
121+
"""Test that ExplicitNullValue in lists is converted to None"""
122+
test_list = ["value", ExplicitNullValue(), ExplicitNullValue, 123]
123+
result = _flatten_request_body_recursive(test_list)
124+
self.assertEqual(result, ["value", None, None, 123])
125+
126+
def test_flatten_mapped_object(self):
127+
"""Test that MappedObject is serialized"""
128+
mapped_obj = MappedObject(key1="value1", key2=123)
129+
result = _flatten_request_body_recursive(mapped_obj)
130+
self.assertEqual(result, {"key1": "value1", "key2": 123})
131+
132+
def test_flatten_mapped_object_nested(self):
133+
"""Test that nested MappedObject is serialized"""
134+
mapped_obj = MappedObject(
135+
outer="value", inner={"nested_key": "nested_value"}
136+
)
137+
result = _flatten_request_body_recursive(mapped_obj)
138+
# The inner dict becomes a MappedObject when created
139+
self.assertIn("outer", result)
140+
self.assertEqual(result["outer"], "value")
141+
self.assertIn("inner", result)
142+
143+
def test_flatten_mapped_object_in_dict(self):
144+
"""Test that MappedObject in dicts is serialized"""
145+
mapped_obj = MappedObject(key="value")
146+
test_dict = {"field": mapped_obj, "other": "data"}
147+
result = _flatten_request_body_recursive(test_dict)
148+
self.assertEqual(result, {"field": {"key": "value"}, "other": "data"})
149+
150+
def test_flatten_mapped_object_in_list(self):
151+
"""Test that MappedObject in lists is serialized"""
152+
mapped_obj = MappedObject(key="value")
153+
test_list = [mapped_obj, "string", 123]
154+
result = _flatten_request_body_recursive(test_list)
155+
self.assertEqual(result, [{"key": "value"}, "string", 123])
156+
157+
def test_flatten_json_object(self):
158+
"""Test that JSONObject subclasses are serialized"""
159+
160+
@dataclass
161+
class TestJSONObject(JSONObject):
162+
field1: str = ""
163+
field2: int = 0
164+
165+
json_obj = TestJSONObject.from_json({"field1": "test", "field2": 42})
166+
result = _flatten_request_body_recursive(json_obj)
167+
self.assertEqual(result, {"field1": "test", "field2": 42})
168+
169+
def test_flatten_json_object_in_dict(self):
170+
"""Test that JSONObject in dicts is serialized"""
171+
172+
@dataclass
173+
class TestJSONObject(JSONObject):
174+
name: str = ""
175+
176+
json_obj = TestJSONObject.from_json({"name": "test"})
177+
test_dict = {"obj": json_obj, "value": 123}
178+
result = _flatten_request_body_recursive(test_dict)
179+
self.assertEqual(result, {"obj": {"name": "test"}, "value": 123})
180+
181+
def test_flatten_json_object_in_list(self):
182+
"""Test that JSONObject in lists is serialized"""
183+
184+
@dataclass
185+
class TestJSONObject(JSONObject):
186+
id: int = 0
187+
188+
json_obj = TestJSONObject.from_json({"id": 999})
189+
test_list = [json_obj, "text"]
190+
result = _flatten_request_body_recursive(test_list)
191+
self.assertEqual(result, [{"id": 999}, "text"])
192+
193+
def test_flatten_complex_nested_structure(self):
194+
"""Test a complex nested structure with multiple types"""
195+
196+
class TestBase(Base):
197+
api_endpoint = "/test/{id}"
198+
properties = {
199+
"id": Property(identifier=True),
200+
}
201+
202+
@dataclass
203+
class TestJSONObject(JSONObject):
204+
value: str = ""
205+
206+
base_obj = TestBase(self.client, 555)
207+
mapped_obj = MappedObject(key="mapped")
208+
json_obj = TestJSONObject.from_json({"value": "json"})
209+
210+
complex_structure = {
211+
"base": base_obj,
212+
"mapped": mapped_obj,
213+
"json": json_obj,
214+
"null": ExplicitNullValue(),
215+
"list": [base_obj, mapped_obj, json_obj, ExplicitNullValue],
216+
"nested": {
217+
"deep": {
218+
"base": base_obj,
219+
"primitives": [1, "two", 3.0],
220+
}
221+
},
222+
}
223+
224+
result = _flatten_request_body_recursive(complex_structure)
225+
226+
self.assertEqual(result["base"], 555)
227+
self.assertEqual(result["mapped"], {"key": "mapped"})
228+
self.assertEqual(result["json"], {"value": "json"})
229+
self.assertIsNone(result["null"])
230+
self.assertEqual(
231+
result["list"], [555, {"key": "mapped"}, {"value": "json"}, None]
232+
)
233+
self.assertEqual(result["nested"]["deep"]["base"], 555)
234+
self.assertEqual(
235+
result["nested"]["deep"]["primitives"], [1, "two", 3.0]
236+
)
237+
238+
def test_flatten_with_is_put_false(self):
239+
"""Test that is_put parameter is passed through"""
240+
241+
@dataclass
242+
class TestJSONObject(JSONObject):
243+
field: str = ""
244+
245+
def _serialize(self, is_put=False):
246+
return {"field": self.field, "is_put": is_put}
247+
248+
json_obj = TestJSONObject.from_json({"field": "test"})
249+
result = _flatten_request_body_recursive(json_obj, is_put=False)
250+
self.assertEqual(result, {"field": "test", "is_put": False})
251+
252+
def test_flatten_with_is_put_true(self):
253+
"""Test that is_put=True parameter is passed through"""
254+
255+
@dataclass
256+
class TestJSONObject(JSONObject):
257+
field: str = ""
258+
259+
def _serialize(self, is_put=False):
260+
return {"field": self.field, "is_put": is_put}
261+
262+
json_obj = TestJSONObject.from_json({"field": "test"})
263+
result = _flatten_request_body_recursive(json_obj, is_put=True)
264+
self.assertEqual(result, {"field": "test", "is_put": True})
265+
266+
def test_flatten_empty_dict(self):
267+
"""Test that empty dicts are handled correctly"""
268+
result = _flatten_request_body_recursive({})
269+
self.assertEqual(result, {})
270+
271+
def test_flatten_empty_list(self):
272+
"""Test that empty lists are handled correctly"""
273+
result = _flatten_request_body_recursive([])
274+
self.assertEqual(result, [])
275+
276+
def test_flatten_dict_with_none_values(self):
277+
"""Test that None values in dicts are preserved"""
278+
test_dict = {"key1": "value", "key2": None, "key3": 0}
279+
result = _flatten_request_body_recursive(test_dict)
280+
self.assertEqual(result, test_dict)
281+
282+
def test_flatten_list_with_none_values(self):
283+
"""Test that None values in lists are preserved"""
284+
test_list = ["value", None, 0, ""]
285+
result = _flatten_request_body_recursive(test_list)
286+
self.assertEqual(result, test_list)

0 commit comments

Comments
 (0)