diff --git a/linode_api4/groups/image.py b/linode_api4/groups/image.py index e644dc16..fda56fb0 100644 --- a/linode_api4/groups/image.py +++ b/linode_api4/groups/image.py @@ -4,7 +4,8 @@ from linode_api4.errors import UnexpectedResponseError from linode_api4.groups import Group -from linode_api4.objects import Base, Disk, Image +from linode_api4.objects import Disk, Image +from linode_api4.objects.base import _flatten_request_body_recursive from linode_api4.util import drop_null_keys @@ -58,7 +59,7 @@ def create( :rtype: Image """ params = { - "disk_id": disk.id if issubclass(type(disk), Base) else disk, + "disk_id": disk, "label": label, "description": description, "tags": tags, @@ -67,7 +68,10 @@ def create( if cloud_init: params["cloud_init"] = cloud_init - result = self.client.post("/images", data=drop_null_keys(params)) + result = self.client.post( + "/images", + data=_flatten_request_body_recursive(drop_null_keys(params)), + ) if not "id" in result: raise UnexpectedResponseError( diff --git a/linode_api4/groups/linode.py b/linode_api4/groups/linode.py index da3ba501..48f0d43b 100644 --- a/linode_api4/groups/linode.py +++ b/linode_api4/groups/linode.py @@ -1,25 +1,29 @@ import base64 import os from collections.abc import Iterable -from typing import Optional, Union +from typing import Any, Dict, Optional, Union -from linode_api4 import InstanceDiskEncryptionType from linode_api4.common import load_and_validate_keys from linode_api4.errors import UnexpectedResponseError from linode_api4.groups import Group from linode_api4.objects import ( - Base, ConfigInterface, Firewall, - Image, Instance, + InstanceDiskEncryptionType, Kernel, + PlacementGroup, StackScript, Type, ) +from linode_api4.objects.base import _flatten_request_body_recursive from linode_api4.objects.filtering import Filter -from linode_api4.objects.linode import _expand_placement_group_assignment -from linode_api4.paginated_list import PaginatedList +from linode_api4.objects.linode import ( + Backup, + InstancePlacementGroupAssignment, + _expand_placement_group_assignment, +) +from linode_api4.util import drop_null_keys class LinodeGroup(Group): @@ -135,9 +139,20 @@ def instance_create( region, image=None, authorized_keys=None, + firewall: Optional[Union[Firewall, int]] = None, + backup: Optional[Union[Backup, int]] = None, + stackscript: Optional[Union[StackScript, int]] = None, disk_encryption: Optional[ Union[InstanceDiskEncryptionType, str] ] = None, + placement_group: Optional[ + Union[ + InstancePlacementGroupAssignment, + PlacementGroup, + Dict[str, Any], + int, + ] + ] = None, **kwargs, ): """ @@ -290,65 +305,45 @@ def instance_create( This usually indicates that you are using an outdated library. """ + ret_pass = None if image and not "root_pass" in kwargs: ret_pass = Instance.generate_root_password() kwargs["root_pass"] = ret_pass - authorized_keys = load_and_validate_keys(authorized_keys) - - if "stackscript" in kwargs: - # translate stackscripts - kwargs["stackscript_id"] = ( - kwargs["stackscript"].id - if issubclass(type(kwargs["stackscript"]), Base) - else kwargs["stackscript"] - ) - del kwargs["stackscript"] - - if "backup" in kwargs: - # translate backups - kwargs["backup_id"] = ( - kwargs["backup"].id - if issubclass(type(kwargs["backup"]), Base) - else kwargs["backup"] - ) - del kwargs["backup"] - - if "firewall" in kwargs: - fw = kwargs.pop("firewall") - kwargs["firewall_id"] = fw.id if isinstance(fw, Firewall) else fw - - if "interfaces" in kwargs: - interfaces = kwargs.get("interfaces") - if interfaces is not None and isinstance(interfaces, Iterable): - kwargs["interfaces"] = [ - i._serialize() if isinstance(i, ConfigInterface) else i - for i in interfaces - ] - - if "placement_group" in kwargs: - kwargs["placement_group"] = _expand_placement_group_assignment( - kwargs.get("placement_group") - ) + interfaces = kwargs.get("interfaces", None) + if interfaces is not None and isinstance(interfaces, Iterable): + kwargs["interfaces"] = [ + i._serialize() if isinstance(i, ConfigInterface) else i + for i in interfaces + ] params = { - "type": ltype.id if issubclass(type(ltype), Base) else ltype, - "region": region.id if issubclass(type(region), Base) else region, - "image": ( - (image.id if issubclass(type(image), Base) else image) - if image + "type": ltype, + "region": region, + "image": image, + "authorized_keys": load_and_validate_keys(authorized_keys), + # These will automatically be flattened below + "firewall_id": firewall, + "backup_id": backup, + "stackscript_id": stackscript, + # Special cases + "disk_encryption": ( + str(disk_encryption) if disk_encryption else None + ), + "placement_group": ( + _expand_placement_group_assignment(placement_group) + if placement_group else None ), - "authorized_keys": authorized_keys, } - if disk_encryption is not None: - params["disk_encryption"] = str(disk_encryption) - params.update(kwargs) - result = self.client.post("/linode/instances", data=params) + result = self.client.post( + "/linode/instances", + data=_flatten_request_body_recursive(drop_null_keys(params)), + ) if not "id" in result: raise UnexpectedResponseError( @@ -421,19 +416,6 @@ def stackscript_create( :returns: The new StackScript :rtype: StackScript """ - image_list = None - if type(images) is list or type(images) is PaginatedList: - image_list = [ - d.id if issubclass(type(d), Base) else d for d in images - ] - elif type(images) is Image: - image_list = [images.id] - elif type(images) is str: - image_list = [images] - else: - raise ValueError( - "images must be a list of Images or a single Image" - ) script_body = script if not script.startswith("#!"): @@ -448,14 +430,17 @@ def stackscript_create( params = { "label": label, - "images": image_list, + "images": images, "is_public": public, "script": script_body, "description": desc if desc else "", } params.update(kwargs) - result = self.client.post("/linode/stackscripts", data=params) + result = self.client.post( + "/linode/stackscripts", + data=_flatten_request_body_recursive(params), + ) if not "id" in result: raise UnexpectedResponseError( diff --git a/linode_api4/groups/volume.py b/linode_api4/groups/volume.py index 6e879c3d..39d0aeaa 100644 --- a/linode_api4/groups/volume.py +++ b/linode_api4/groups/volume.py @@ -1,6 +1,7 @@ from linode_api4.errors import UnexpectedResponseError from linode_api4.groups import Group -from linode_api4.objects import Base, Volume, VolumeType +from linode_api4.objects import Volume, VolumeType +from linode_api4.objects.base import _flatten_request_body_recursive class VolumeGroup(Group): @@ -57,14 +58,15 @@ def create(self, label, region=None, linode=None, size=20, **kwargs): params = { "label": label, "size": size, - "region": region.id if issubclass(type(region), Base) else region, - "linode_id": ( - linode.id if issubclass(type(linode), Base) else linode - ), + "region": region, + "linode_id": linode, } params.update(kwargs) - result = self.client.post("/volumes", data=params) + result = self.client.post( + "/volumes", + data=_flatten_request_body_recursive(params), + ) if not "id" in result: raise UnexpectedResponseError( diff --git a/linode_api4/objects/linode.py b/linode_api4/objects/linode.py index cb5c9d9a..8fe71bb7 100644 --- a/linode_api4/objects/linode.py +++ b/linode_api4/objects/linode.py @@ -8,10 +8,14 @@ from typing import Any, Dict, List, Optional, Union from urllib import parse -from linode_api4 import util from linode_api4.common import load_and_validate_keys from linode_api4.errors import UnexpectedResponseError -from linode_api4.objects.base import Base, MappedObject, Property +from linode_api4.objects.base import ( + Base, + MappedObject, + Property, + _flatten_request_body_recursive, +) from linode_api4.objects.dbase import DerivedBase from linode_api4.objects.filtering import FilterableAttribute from linode_api4.objects.image import Image @@ -26,6 +30,7 @@ from linode_api4.objects.serializable import JSONObject, StrEnum from linode_api4.objects.vpc import VPC, VPCSubnet from linode_api4.paginated_list import PaginatedList +from linode_api4.util import drop_null_keys PASSWORD_CHARS = string.ascii_letters + string.digits + string.punctuation @@ -96,14 +101,14 @@ def restore_to(self, linode, **kwargs): """ d = { - "linode_id": ( - linode.id if issubclass(type(linode), Base) else linode - ), + "linode_id": linode, } d.update(kwargs) self._client.post( - "{}/restore".format(Backup.api_endpoint), model=self, data=d + "{}/restore".format(Backup.api_endpoint), + model=self, + data=_flatten_request_body_recursive(d), ) return True @@ -1063,8 +1068,6 @@ def resize( :rtype: bool """ - new_type = new_type.id if issubclass(type(new_type), Base) else new_type - params = { "type": new_type, "allow_auto_disk_resize": allow_auto_disk_resize, @@ -1073,7 +1076,9 @@ def resize( params.update(kwargs) resp = self._client.post( - "{}/resize".format(Instance.api_endpoint), model=self, data=params + "{}/resize".format(Instance.api_endpoint), + model=self, + data=_flatten_request_body_recursive(params), ) if "error" in resp: @@ -1189,7 +1194,7 @@ def config_create( param_interfaces.append(interface) params = { - "kernel": kernel.id if issubclass(type(kernel), Base) else kernel, + "kernel": kernel, "label": ( label if label @@ -1201,7 +1206,9 @@ def config_create( params.update(kwargs) result = self._client.post( - "{}/configs".format(Instance.api_endpoint), model=self, data=params + "{}/configs".format(Instance.api_endpoint), + model=self, + data=_flatten_request_body_recursive(params), ) self.invalidate() @@ -1280,6 +1287,7 @@ def disk_create( "filesystem": filesystem, "authorized_keys": authorized_keys, "authorized_users": authorized_users, + "stackscript_id": stackscript, } if disk_encryption is not None: @@ -1288,20 +1296,18 @@ def disk_create( if image: params.update( { - "image": ( - image.id if issubclass(type(image), Base) else image - ), + "image": image, "root_pass": root_pass, } ) - if stackscript: - params["stackscript_id"] = stackscript.id - if stackscript_args: - params["stackscript_data"] = stackscript_args + if stackscript_args: + params["stackscript_data"] = stackscript_args result = self._client.post( - "{}/disks".format(Instance.api_endpoint), model=self, data=params + "{}/disks".format(Instance.api_endpoint), + model=self, + data=_flatten_request_body_recursive(drop_null_keys(params)), ) self.invalidate() @@ -1461,18 +1467,20 @@ def rebuild( authorized_keys = load_and_validate_keys(authorized_keys) params = { - "image": image.id if issubclass(type(image), Base) else image, + "image": image, "root_pass": root_pass, "authorized_keys": authorized_keys, + "disk_encryption": ( + str(disk_encryption) if disk_encryption else None + ), } - if disk_encryption is not None: - params["disk_encryption"] = str(disk_encryption) - params.update(kwargs) result = self._client.post( - "{}/rebuild".format(Instance.api_endpoint), model=self, data=params + "{}/rebuild".format(Instance.api_endpoint), + model=self, + data=_flatten_request_body_recursive(drop_null_keys(params)), ) if not "id" in result: @@ -1597,7 +1605,7 @@ def initiate_migration( """ params = { - "region": region.id if issubclass(type(region), Base) else region, + "region": region, "upgrade": upgrade, "type": migration_type, "placement_group": _expand_placement_group_assignment( @@ -1605,10 +1613,10 @@ def initiate_migration( ), } - util.drop_null_keys(params) - self._client.post( - "{}/migrate".format(Instance.api_endpoint), model=self, data=params + "{}/migrate".format(Instance.api_endpoint), + model=self, + data=_flatten_request_body_recursive(drop_null_keys(params)), ) def firewalls(self): @@ -1740,21 +1748,12 @@ def clone( if not isinstance(disks, list) and not isinstance(disks, PaginatedList): disks = [disks] - cids = [c.id if issubclass(type(c), Base) else c for c in configs] - dids = [d.id if issubclass(type(d), Base) else d for d in disks] - params = { - "linode_id": ( - to_linode.id if issubclass(type(to_linode), Base) else to_linode - ), - "region": region.id if issubclass(type(region), Base) else region, - "type": ( - instance_type.id - if issubclass(type(instance_type), Base) - else instance_type - ), - "configs": cids if cids else None, - "disks": dids if dids else None, + "linode_id": to_linode, + "region": region, + "type": instance_type, + "configs": configs, + "disks": disks, "label": label, "group": group, "with_backups": with_backups, @@ -1763,10 +1762,10 @@ def clone( ), } - util.drop_null_keys(params) - result = self._client.post( - "{}/clone".format(Instance.api_endpoint), model=self, data=params + "{}/clone".format(Instance.api_endpoint), + model=self, + data=_flatten_request_body_recursive(drop_null_keys(params)), ) if not "id" in result: diff --git a/linode_api4/objects/volume.py b/linode_api4/objects/volume.py index 6d49f72c..cda9932a 100644 --- a/linode_api4/objects/volume.py +++ b/linode_api4/objects/volume.py @@ -1,8 +1,13 @@ from linode_api4.common import Price, RegionPrice from linode_api4.errors import UnexpectedResponseError -from linode_api4.objects.base import Base, Property +from linode_api4.objects.base import ( + Base, + Property, + _flatten_request_body_recursive, +) from linode_api4.objects.linode import Instance, Region from linode_api4.objects.region import Region +from linode_api4.util import drop_null_keys class VolumeType(Base): @@ -63,21 +68,16 @@ def attach(self, to_linode, config=None): If not given, the last booted Config will be chosen. :type config: Union[Config, int] """ + + body = { + "linode_id": to_linode, + "config": config, + } + result = self._client.post( "{}/attach".format(Volume.api_endpoint), model=self, - data={ - "linode_id": ( - to_linode.id - if issubclass(type(to_linode), Base) - else to_linode - ), - "config": ( - None - if not config - else config.id if issubclass(type(config), Base) else config - ), - }, + data=_flatten_request_body_recursive(drop_null_keys(body)), ) if not "id" in result: