Skip to content

Commit d6abfb5

Browse files
Fix conflict
2 parents 22fdf79 + c8fa2d5 commit d6abfb5

File tree

15 files changed

+174
-38
lines changed

15 files changed

+174
-38
lines changed

linode_api4/objects/account.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from linode_api4.objects.networking import Firewall
1717
from linode_api4.objects.nodebalancer import NodeBalancer
1818
from linode_api4.objects.profile import PersonalAccessToken
19+
from linode_api4.objects.serializable import StrEnum
1920
from linode_api4.objects.support import SupportTicket
2021
from linode_api4.objects.volume import Volume
2122
from linode_api4.objects.vpc import VPC
@@ -179,6 +180,24 @@ class Login(Base):
179180
}
180181

181182

183+
class AccountSettingsInterfacesForNewLinodes(StrEnum):
184+
"""
185+
A string enum corresponding to valid values
186+
for the AccountSettings(...).interfaces_for_new_linodes field.
187+
188+
NOTE: This feature may not currently be available to all users.
189+
"""
190+
191+
legacy_config_only = "legacy_config_only"
192+
legacy_config_default_but_linode_allowed = (
193+
"legacy_config_default_but_linode_allowed"
194+
)
195+
linode_default_but_legacy_config_allowed = (
196+
"linode_default_but_legacy_config_allowed"
197+
)
198+
linode_only = "linode_only"
199+
200+
182201
class AccountSettings(Base):
183202
"""
184203
Information related to your Account settings.
@@ -197,6 +216,7 @@ class AccountSettings(Base):
197216
),
198217
"object_storage": Property(),
199218
"backups_enabled": Property(mutable=True),
219+
"interfaces_for_new_linodes": Property(mutable=True),
200220
}
201221

202222

@@ -601,7 +621,7 @@ def entity(self):
601621
)
602622
return self.cls(self._client, self.id)
603623

604-
def _serialize(self):
624+
def _serialize(self, *args, **kwargs):
605625
"""
606626
Returns this grant in as JSON the api will accept. This is only relevant
607627
in the context of UserGrants.save
@@ -668,7 +688,7 @@ def _grants_dict(self):
668688

669689
return grants
670690

671-
def _serialize(self):
691+
def _serialize(self, *args, **kwargs):
672692
"""
673693
Returns the user grants in as JSON the api will accept.
674694
This is only relevant in the context of UserGrants.save

linode_api4/objects/base.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ def _flatten_base_subclass(obj: "Base") -> Optional[Dict[str, Any]]:
114114

115115
@property
116116
def dict(self):
117+
return self._serialize()
118+
119+
def _serialize(self, is_put: bool = False) -> Dict[str, Any]:
117120
result = vars(self).copy()
118121
cls = type(self)
119122

@@ -123,7 +126,7 @@ def dict(self):
123126
elif isinstance(v, list):
124127
result[k] = [
125128
(
126-
item.dict
129+
item._serialize(is_put=is_put)
127130
if isinstance(item, (cls, JSONObject))
128131
else (
129132
self._flatten_base_subclass(item)
@@ -136,7 +139,7 @@ def dict(self):
136139
elif isinstance(v, Base):
137140
result[k] = self._flatten_base_subclass(v)
138141
elif isinstance(v, JSONObject):
139-
result[k] = v.dict
142+
result[k] = v._serialize(is_put=is_put)
140143

141144
return result
142145

@@ -278,9 +281,9 @@ def save(self, force=True) -> bool:
278281
data[key] = None
279282

280283
# Ensure we serialize any values that may not be already serialized
281-
data = _flatten_request_body_recursive(data)
284+
data = _flatten_request_body_recursive(data, is_put=True)
282285
else:
283-
data = self._serialize()
286+
data = self._serialize(is_put=True)
284287

285288
resp = self._client.put(type(self).api_endpoint, model=self, data=data)
286289

@@ -316,7 +319,7 @@ def invalidate(self):
316319

317320
self._set("_populated", False)
318321

319-
def _serialize(self):
322+
def _serialize(self, is_put: bool = False):
320323
"""
321324
A helper method to build a dict of all mutable Properties of
322325
this object
@@ -345,7 +348,7 @@ def _serialize(self):
345348

346349
# Resolve the underlying IDs of results
347350
for k, v in result.items():
348-
result[k] = _flatten_request_body_recursive(v)
351+
result[k] = _flatten_request_body_recursive(v, is_put=is_put)
349352

350353
return result
351354

@@ -503,7 +506,7 @@ def make_instance(cls, id, client, parent_id=None, json=None):
503506
return Base.make(id, client, cls, parent_id=parent_id, json=json)
504507

505508

506-
def _flatten_request_body_recursive(data: Any) -> Any:
509+
def _flatten_request_body_recursive(data: Any, is_put: bool = False) -> Any:
507510
"""
508511
This is a helper recursively flatten the given data for use in an API request body.
509512
@@ -515,15 +518,18 @@ def _flatten_request_body_recursive(data: Any) -> Any:
515518
"""
516519

517520
if isinstance(data, dict):
518-
return {k: _flatten_request_body_recursive(v) for k, v in data.items()}
521+
return {
522+
k: _flatten_request_body_recursive(v, is_put=is_put)
523+
for k, v in data.items()
524+
}
519525

520526
if isinstance(data, list):
521-
return [_flatten_request_body_recursive(v) for v in data]
527+
return [_flatten_request_body_recursive(v, is_put=is_put) for v in data]
522528

523529
if isinstance(data, Base):
524530
return data.id
525531

526532
if isinstance(data, MappedObject) or issubclass(type(data), JSONObject):
527-
return data.dict
533+
return data._serialize(is_put=is_put)
528534

529535
return data

linode_api4/objects/linode.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ class ConfigInterface(JSONObject):
408408
def __repr__(self):
409409
return f"Interface: {self.purpose}"
410410

411-
def _serialize(self):
411+
def _serialize(self, *args, **kwargs):
412412
purpose_formats = {
413413
"public": {"purpose": "public", "primary": self.primary},
414414
"vlan": {
@@ -519,16 +519,16 @@ def _populate(self, json):
519519

520520
self._set("devices", MappedObject(**devices))
521521

522-
def _serialize(self):
522+
def _serialize(self, is_put: bool = False):
523523
"""
524524
Overrides _serialize to transform interfaces into json
525525
"""
526-
partial = DerivedBase._serialize(self)
526+
partial = DerivedBase._serialize(self, is_put=is_put)
527527
interfaces = []
528528

529529
for c in self.interfaces:
530530
if isinstance(c, ConfigInterface):
531-
interfaces.append(c._serialize())
531+
interfaces.append(c._serialize(is_put=is_put))
532532
else:
533533
interfaces.append(c)
534534

@@ -2068,8 +2068,8 @@ def _populate(self, json):
20682068
ndist = [Image(self._client, d) for d in self.images]
20692069
self._set("images", ndist)
20702070

2071-
def _serialize(self):
2072-
dct = Base._serialize(self)
2071+
def _serialize(self, is_put: bool = False):
2072+
dct = Base._serialize(self, is_put=is_put)
20732073
dct["images"] = [d.id for d in self.images]
20742074
return dct
20752075

linode_api4/objects/serializable.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import inspect
2-
from dataclasses import dataclass
2+
from dataclasses import dataclass, fields
33
from enum import Enum
44
from types import SimpleNamespace
55
from typing import (
@@ -9,6 +9,7 @@
99
List,
1010
Optional,
1111
Set,
12+
Type,
1213
Union,
1314
get_args,
1415
get_origin,
@@ -71,6 +72,13 @@ class JSONObject(metaclass=JSONFilterableMetaclass):
7172
are None.
7273
"""
7374

75+
put_class: ClassVar[Optional[Type["JSONObject"]]] = None
76+
"""
77+
An alternative JSONObject class to use as the schema for PUT requests.
78+
This prevents read-only fields from being included in PUT request bodies,
79+
which in theory will result in validation errors from the API.
80+
"""
81+
7482
def __init__(self):
7583
raise NotImplementedError(
7684
"JSONObject is not intended to be constructed directly"
@@ -154,19 +162,25 @@ def from_json(cls, json: Dict[str, Any]) -> Optional["JSONObject"]:
154162

155163
return obj
156164

157-
def _serialize(self) -> Dict[str, Any]:
165+
def _serialize(self, is_put: bool = False) -> Dict[str, Any]:
158166
"""
159167
Serializes this object into a JSON dict.
160168
"""
161169
cls = type(self)
170+
171+
if is_put and cls.put_class is not None:
172+
cls = cls.put_class
173+
174+
cls_field_keys = {field.name for field in fields(cls)}
175+
162176
type_hints = get_type_hints(cls)
163177

164178
def attempt_serialize(value: Any) -> Any:
165179
"""
166180
Attempts to serialize the given value, else returns the value unchanged.
167181
"""
168182
if issubclass(type(value), JSONObject):
169-
return value._serialize()
183+
return value._serialize(is_put=is_put)
170184

171185
return value
172186

@@ -175,6 +189,10 @@ def should_include(key: str, value: Any) -> bool:
175189
Returns whether the given key/value pair should be included in the resulting dict.
176190
"""
177191

192+
# During PUT operations, keys not present in the put_class should be excluded
193+
if key not in cls_field_keys:
194+
return False
195+
178196
if cls.include_none_values or key in cls.always_include:
179197
return True
180198

test/fixtures/account.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"Linodes",
1717
"NodeBalancers",
1818
"Block Storage",
19-
"Object Storage"
19+
"Object Storage",
20+
"Linode Interfaces"
2021
],
2122
"active_promotions": [
2223
{

test/fixtures/account_settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"managed": false,
44
"network_helper": false,
55
"object_storage": "active",
6-
"backups_enabled": true
6+
"backups_enabled": true,
7+
"interfaces_for_new_linodes": "linode_default_but_legacy_config_allowed"
78
}

test/integration/login_client/test_login_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def test_linode_login_client_generate_login_url_with_scope(linode_login_client):
9797
assert "scopes=linodes%3Aread_write" in url
9898

9999

100+
@pytest.mark.skip("Endpoint may be deprecated")
100101
def test_linode_login_client_expire_token(
101102
linode_login_client, test_oauth_client
102103
):

test/integration/models/account/test_account.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def test_get_account_settings(test_linode_client):
5959
assert "longview_subscription" in str(account_settings._raw_json)
6060
assert "backups_enabled" in str(account_settings._raw_json)
6161
assert "object_storage" in str(account_settings._raw_json)
62+
assert isinstance(account_settings.interfaces_for_new_linodes, str)
6263

6364

6465
@pytest.mark.smoke

test/integration/models/domain/test_domain.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ def test_save_null_values_excluded(test_linode_client, test_domain):
2323
domain.master_ips = ["127.0.0.1"]
2424
res = domain.save()
2525

26-
assert res
27-
2826

2927
def test_zone_file_view(test_linode_client, test_domain):
3028
domain = test_linode_client.load(Domain, test_domain.id)

test/integration/models/linode/test_linode.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import pytest
1111

12-
from linode_api4 import VPCIPAddress
1312
from linode_api4.errors import ApiError
1413
from linode_api4.objects import (
1514
Config,
@@ -181,7 +180,7 @@ def create_linode_for_long_running_tests(test_linode_client, e2e_test_firewall):
181180
def linode_with_disk_encryption(test_linode_client, request):
182181
client = test_linode_client
183182

184-
target_region = get_region(client, {"Disk Encryption"})
183+
target_region = get_region(client, {"LA Disk Encryption"})
185184
label = get_test_label(length=8)
186185

187186
disk_encryption = request.param
@@ -236,7 +235,7 @@ def test_linode_transfer(test_linode_client, linode_with_volume_firewall):
236235
def test_linode_rebuild(test_linode_client):
237236
client = test_linode_client
238237

239-
region = get_region(client, {"Disk Encryption"})
238+
region = get_region(client, {"LA Disk Encryption"})
240239

241240
label = get_test_label() + "_rebuild"
242241

@@ -535,6 +534,7 @@ def test_linode_create_disk(test_linode_client, linode_for_disk_tests):
535534
assert disk.linode_id == linode.id
536535

537536

537+
@pytest.mark.flaky(reruns=3, reruns_delay=2)
538538
def test_linode_instance_password(create_linode_for_pass_reset):
539539
linode = create_linode_for_pass_reset[0]
540540
password = create_linode_for_pass_reset[1]
@@ -775,10 +775,10 @@ def test_create_vpc(
775775
assert vpc_range_ip.address_range == "10.0.0.5/32"
776776
assert not vpc_range_ip.active
777777

778+
# TODO:: Add `VPCIPAddress.filters.linode_id == linode.id` filter back
779+
778780
# Attempt to resolve the IP from /vpcs/ips
779-
all_vpc_ips = test_linode_client.vpcs.ips(
780-
VPCIPAddress.filters.linode_id == linode.id
781-
)
781+
all_vpc_ips = test_linode_client.vpcs.ips()
782782
assert all_vpc_ips[0].dict == vpc_ip.dict
783783

784784
# Test getting the ips under this specific VPC

test/integration/models/lke/test_lke.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def lke_cluster(test_linode_client):
3232
node_type = test_linode_client.linode.types()[1] # g6-standard-1
3333
version = test_linode_client.lke.versions()[0]
3434

35-
region = get_region(test_linode_client, {"Kubernetes", "Disk Encryption"})
35+
region = get_region(
36+
test_linode_client, {"Kubernetes", "LA Disk Encryption"}
37+
)
3638

3739
node_pools = test_linode_client.lke.node_pool(node_type, 3)
3840
label = get_test_label() + "_cluster"
@@ -115,7 +117,9 @@ def lke_cluster_with_labels_and_taints(test_linode_client):
115117
def lke_cluster_with_apl(test_linode_client):
116118
version = test_linode_client.lke.versions()[0]
117119

118-
region = get_region(test_linode_client, {"Kubernetes", "Disk Encryption"})
120+
region = get_region(
121+
test_linode_client, {"Kubernetes", "LA Disk Encryption"}
122+
)
119123

120124
# NOTE: g6-dedicated-4 is the minimum APL-compatible Linode type
121125
node_pools = test_linode_client.lke.node_pool("g6-dedicated-4", 3)
@@ -145,7 +149,7 @@ def lke_cluster_enterprise(test_linode_client):
145149
)[0]
146150

147151
region = get_region(
148-
test_linode_client, {"Kubernetes Enterprise", "Disk Encryption"}
152+
test_linode_client, {"Kubernetes Enterprise", "LA Disk Encryption"}
149153
)
150154

151155
node_pools = test_linode_client.lke.node_pool(
@@ -204,7 +208,7 @@ def _to_comparable(p: LKENodePool) -> Dict[str, Any]:
204208

205209
assert _to_comparable(cluster.pools[0]) == _to_comparable(pool)
206210

207-
assert pool.disk_encryption == InstanceDiskEncryptionType.enabled
211+
assert pool.disk_encryption == InstanceDiskEncryptionType.disabled
208212

209213

210214
def test_cluster_dashboard_url_view(lke_cluster):

0 commit comments

Comments
 (0)