From 54e37034622bd87ec590187451e3245d30e2f97c Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Thu, 26 Sep 2024 01:36:51 +0200 Subject: [PATCH] [#344] Removed remaining references of `marshmallow_dataclass`. Closes: #344 --- platypush/plugins/linode/__init__.py | 28 +- platypush/schemas/linode.py | 421 --------------------------- platypush/schemas/linode/__init__.py | 9 + platypush/schemas/linode/_model.py | 136 +++++++++ platypush/schemas/linode/_schemas.py | 349 ++++++++++++++++++++++ platypush/utils/mock/modules.py | 1 - requirements.txt | 1 - 7 files changed, 510 insertions(+), 435 deletions(-) delete mode 100644 platypush/schemas/linode.py create mode 100644 platypush/schemas/linode/__init__.py create mode 100644 platypush/schemas/linode/_model.py create mode 100644 platypush/schemas/linode/_schemas.py diff --git a/platypush/plugins/linode/__init__.py b/platypush/plugins/linode/__init__.py index 2214e7cc32..df2f59e4d6 100644 --- a/platypush/plugins/linode/__init__.py +++ b/platypush/plugins/linode/__init__.py @@ -82,10 +82,13 @@ class LinodePlugin(RunnablePlugin, CloudInstanceEntityManager, EnumSwitchEntityM instances = [] while not self.should_stop(): - status = {instance.id: instance for instance in instances} + status = { + getattr(instance, 'id', instance.get('id')): instance + for instance in instances + } new_status = { - instance.id: instance + instance['id']: instance for instance in self.status(publish_entities=False).output } @@ -94,8 +97,8 @@ class LinodePlugin(RunnablePlugin, CloudInstanceEntityManager, EnumSwitchEntityM instance for instance in new_status.values() if not ( - status.get(instance.id) - and status[instance.id].status == instance.status + status.get(instance['id']) + and status[instance['id']].status == instance.status ) ] if new_status @@ -106,12 +109,12 @@ class LinodePlugin(RunnablePlugin, CloudInstanceEntityManager, EnumSwitchEntityM for instance in changed_instances: get_bus().post( LinodeInstanceStatusChanged( - instance_id=instance.id, - instance_name=instance.name, - status=instance.status, + instance_id=instance['id'], + instance_name=instance['name'], + status=instance['status'], old_status=( - status[instance.id].status - if status.get(instance.id) + status[instance['id']]['status'] + if status.get(instance['id']) else None ), ) @@ -123,16 +126,17 @@ class LinodePlugin(RunnablePlugin, CloudInstanceEntityManager, EnumSwitchEntityM self.wait_stop(self.poll_interval) def transform_entities( - self, entities: Collection[LinodeInstance] + self, entities: Collection[dict], **_ ) -> Collection[CloudInstance]: schema = LinodeInstanceSchema() + print(schema.dump(entities, many=True)) return super().transform_entities( [ CloudInstance( - reachable=instance.status == LinodeInstanceStatus.RUNNING, + reachable=instance['status'] == LinodeInstanceStatus.RUNNING, children=[ EnumSwitch( - id=f'{instance.id}:__action__', + id=f'{instance["id"]}:__action__', name='Actions', values=['boot', 'reboot', 'shutdown'], is_write_only=True, diff --git a/platypush/schemas/linode.py b/platypush/schemas/linode.py deleted file mode 100644 index 026322edd3..0000000000 --- a/platypush/schemas/linode.py +++ /dev/null @@ -1,421 +0,0 @@ -from dataclasses import dataclass, field -from datetime import datetime -from enum import Enum -from functools import partial -from typing import Any, List, Optional - -from marshmallow import pre_load -from marshmallow.fields import Function -from marshmallow.validate import Range -from marshmallow_dataclass import class_schema - -from platypush.schemas import EnumField -from platypush.schemas.dataclasses import DataClassSchema - - -class LinodeInstanceStatus(Enum): - """ - Maps the possible states of an instance. - """ - - RUNNING = 'running' - OFFLINE = 'offline' - BOOTING = 'booting' - REBOOTING = 'rebooting' - SHUTTING_DOWN = 'shutting_down' - PROVISIONING = 'provisioning' - DELETING = 'deleting' - MIGRATING = 'migrating' - REBUILDING = 'rebuilding' - CLONING = 'cloning' - RESTORING = 'restoring' - STOPPED = 'stopped' - - -class LinodeInstanceBackupScheduleDay(Enum): - """ - Allowed values for ``backups.schedule.day``. - """ - - SCHEDULING = 'Scheduling' - SUNDAY = 'Sunday' - MONDAY = 'Monday' - TUESDAY = 'Tuesday' - WEDNESDAY = 'Wednesday' - THURSDAY = 'Thursday' - FRIDAY = 'Friday' - SATURDAY = 'Saturday' - - -class LinodeInstanceBackupScheduleWindow(Enum): - """ - Allowed values for ``backups.schedule.window``. - - The window in which your backups will be taken, in UTC. A backups window is - a two-hour span of time in which the backup may occur. - - For example, W10 indicates that your backups should be taken between 10:00 - and 12:00. - """ - - SCHEDULING = 'Scheduling' - W0 = 'W0' - W2 = 'W2' - W4 = 'W4' - W6 = 'W6' - W8 = 'W8' - W10 = 'W10' - W12 = 'W12' - W14 = 'W14' - W16 = 'W16' - W18 = 'W18' - W20 = 'W20' - W22 = 'W22' - - -class FieldWithId(Function): - """ - Field that handles values that are objects with an ``id`` attribute. - """ - - def _deserialize(self, value: Any, *_, **__) -> Optional[Any]: - return value.id if value is not None else None - - def _serialize(self, value: Any, *_, **__) -> Optional[Any]: - return value - - -class LinodeBaseSchema(DataClassSchema): - """ - Base schema for all Linode objects. - """ - - TYPE_MAPPING = { - LinodeInstanceStatus: partial( # type: ignore - EnumField, type=LinodeInstanceStatus - ), - LinodeInstanceBackupScheduleDay: partial( # type: ignore - EnumField, type=LinodeInstanceBackupScheduleDay - ), - LinodeInstanceBackupScheduleWindow: partial( # type: ignore - EnumField, type=LinodeInstanceBackupScheduleWindow - ), - **DataClassSchema.TYPE_MAPPING, - } - - @pre_load - def pre_load(self, data: dict, **_) -> dict: - from linode_api4.objects.base import MappedObject - - # Expand MappedObjects to dictionaries - for key, value in data.items(): - if isinstance(value, MappedObject): - data[key] = value.dict - - # NOTE Workaround for type -> instance_type not being correctly mapped - if 'type' in data: - data['instance_type'] = data.pop('type') - - return data - - -@dataclass -class LinodeInstanceSpecs: - """ - Class that models the specifications of a Linode instance. - """ - - disk: int = field( - metadata={ - 'metadata': { - 'description': 'Allocated disk size, in MB', - 'example': 100000, - } - } - ) - - memory: int = field( - metadata={ - 'metadata': { - 'description': 'Allocated RAM size, in MB', - 'example': 8192, - } - } - ) - - cpus: int = field( - metadata={ - 'data_key': 'vcpus', - 'metadata': { - 'description': 'Number of virtual CPUs allocated to the instance', - 'example': 4, - }, - } - ) - - gpus: int = field( - metadata={ - 'metadata': { - 'description': 'Number of GPUs allocated to the instance', - 'example': 1, - } - } - ) - - transfer: int = field( - metadata={ - 'metadata': { - 'description': ( - 'Number of network transfers this instance is allotted each month', - ), - 'example': 5000, - } - } - ) - - -@dataclass -class LinodeInstanceAlerts: - """ - Class that models the alerts configuration of a Linode instance. - """ - - cpu: int = field( - metadata={ - 'metadata': { - 'validate': Range(min=0, max=100), - 'description': ( - 'The percentage of CPU average usage over the past two hours ' - 'required to trigger an alert', - ), - 'example': 90, - } - } - ) - - io: int = field( - metadata={ - 'metadata': { - 'description': ( - 'The amount of disk I/O operations per second required to ' - 'trigger an alert' - ), - 'example': 5000, - } - } - ) - - network_in: int = field( - metadata={ - 'metadata': { - 'description': ( - 'The amount of incoming network traffic, in Mbit/s, ' - 'required to trigger an alert' - ), - 'example': 10, - } - } - ) - - network_out: int = field( - metadata={ - 'metadata': { - 'description': ( - 'The amount of outgoing network traffic, in Mbit/s, ' - 'required to trigger an alert' - ), - 'example': 10, - } - } - ) - - transfer_quota: int = field( - metadata={ - 'metadata': { - 'validate': Range(min=0, max=100), - 'description': ( - 'The percentage of network transfer that may be used before ' - 'an alert is triggered', - ), - 'example': 80, - } - } - ) - - -@dataclass -class LinodeInstanceBackupSchedule: - """ - Class that models the backup schedule of a Linode instance. - """ - - day: Optional[LinodeInstanceBackupScheduleDay] - window: Optional[LinodeInstanceBackupScheduleWindow] - - -@dataclass -class LinodeInstanceBackups: - """ - Class that models the backup status of a Linode instance. - """ - - available: bool - enabled: bool = field( - metadata={ - 'metadata': { - 'description': 'Whether the backups are enabled on this instance', - 'example': True, - } - } - ) - - schedule: LinodeInstanceBackupSchedule - last_successful: Optional[datetime] = field( - metadata={ - 'metadata': { - 'description': 'When the last backup was successful', - 'example': '2020-01-01T00:00:00Z', - } - } - ) - - -@dataclass -class LinodeInstance: - """ - Class that models a Linode instance. - """ - - id: int = field( - metadata={ - 'required': True, - 'metadata': { - 'description': 'Instance ID', - 'example': 12345, - }, - } - ) - - name: str = field( - metadata={ - 'required': True, - 'data_key': 'label', - 'metadata': { - 'description': 'Instance name', - 'example': 'my-instance', - }, - }, - ) - - instance_type: str = field( - metadata={ - 'marshmallow_field': FieldWithId(), - 'metadata': { - 'description': 'Instance type', - 'example': 'g6-standard-4', - }, - } - ) - - ipv4_addresses: List[str] = field( - metadata={ - 'data_key': 'ipv4', - 'metadata': { - 'description': 'List of IPv4 addresses associated with this instance', - 'example': '["1.2.3.4"]', - }, - } - ) - - ipv6_address: str = field( - metadata={ - 'data_key': 'ipv6', - 'metadata': { - 'description': 'IPv6 address associated with this instance', - 'example': '1234:5678::9abc:def0:1234:5678/128', - }, - } - ) - - group: str = field( - metadata={ - 'metadata': { - 'description': 'Group the instance belongs to', - 'example': 'my-group', - } - } - ) - - status: LinodeInstanceStatus = field( - metadata={ - 'metadata': { - 'description': 'Instance status', - 'example': 'running', - } - } - ) - - tags: List[str] = field( - metadata={ - 'metadata': { - 'description': 'List of tags associated with this instance', - 'example': '["tag1", "tag2"]', - } - } - ) - - image: str = field( - metadata={ - 'marshmallow_field': FieldWithId(), - 'metadata': { - 'description': 'Image used to ', - 'example': 'linode/archlinux2014.04', - }, - } - ) - - region: str = field( - metadata={ - 'marshmallow_field': FieldWithId(), - 'metadata': { - 'description': 'Region where the instance is located', - 'example': 'eu-west', - }, - } - ) - - hypervisor: str = field( - metadata={ - 'metadata': { - 'description': 'The virtualization engine powering this instance', - 'example': 'kvm', - } - } - ) - - specs: LinodeInstanceSpecs - alerts: LinodeInstanceAlerts - backups: LinodeInstanceBackups - - created_at: datetime = field( - metadata={ - 'data_key': 'created', - 'metadata': { - 'description': 'Instance creation date', - 'example': '2020-01-01T00:00:00Z', - }, - } - ) - - updated_at: datetime = field( - metadata={ - 'data_key': 'updated', - 'metadata': { - 'description': 'When the instance was last polled/updated', - 'example': '2020-01-01T01:00:00Z', - }, - } - ) - - -LinodeInstanceSchema = class_schema(LinodeInstance, base_schema=LinodeBaseSchema) diff --git a/platypush/schemas/linode/__init__.py b/platypush/schemas/linode/__init__.py new file mode 100644 index 0000000000..8488636d7a --- /dev/null +++ b/platypush/schemas/linode/__init__.py @@ -0,0 +1,9 @@ +from ._model import LinodeInstance, LinodeInstanceStatus +from ._schemas import LinodeInstanceSchema + + +__all__ = [ + 'LinodeInstance', + 'LinodeInstanceSchema', + 'LinodeInstanceStatus', +] diff --git a/platypush/schemas/linode/_model.py b/platypush/schemas/linode/_model.py new file mode 100644 index 0000000000..fff1aff782 --- /dev/null +++ b/platypush/schemas/linode/_model.py @@ -0,0 +1,136 @@ +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import List, Optional + + +class LinodeInstanceStatus(Enum): + """ + Maps the possible states of an instance. + """ + + RUNNING = 'running' + OFFLINE = 'offline' + BOOTING = 'booting' + REBOOTING = 'rebooting' + SHUTTING_DOWN = 'shutting_down' + PROVISIONING = 'provisioning' + DELETING = 'deleting' + MIGRATING = 'migrating' + REBUILDING = 'rebuilding' + CLONING = 'cloning' + RESTORING = 'restoring' + STOPPED = 'stopped' + + +class LinodeInstanceBackupScheduleDay(Enum): + """ + Allowed values for ``backups.schedule.day``. + """ + + SCHEDULING = 'Scheduling' + SUNDAY = 'Sunday' + MONDAY = 'Monday' + TUESDAY = 'Tuesday' + WEDNESDAY = 'Wednesday' + THURSDAY = 'Thursday' + FRIDAY = 'Friday' + SATURDAY = 'Saturday' + + +class LinodeInstanceBackupScheduleWindow(Enum): + """ + Allowed values for ``backups.schedule.window``. + + The window in which your backups will be taken, in UTC. A backups window is + a two-hour span of time in which the backup may occur. + + For example, W10 indicates that your backups should be taken between 10:00 + and 12:00. + """ + + SCHEDULING = 'Scheduling' + W0 = 'W0' + W2 = 'W2' + W4 = 'W4' + W6 = 'W6' + W8 = 'W8' + W10 = 'W10' + W12 = 'W12' + W14 = 'W14' + W16 = 'W16' + W18 = 'W18' + W20 = 'W20' + W22 = 'W22' + + +@dataclass +class LinodeInstanceSpecs: + """ + Class that models the specifications of a Linode instance. + """ + + disk: int + memory: int + cpus: int + gpus: int + transfer: int + + +@dataclass +class LinodeInstanceAlerts: + """ + Class that models the alerts configuration of a Linode instance. + """ + + cpu: int + io: int + network_in: int + network_out: int + transfer_quota: int + + +@dataclass +class LinodeInstanceBackupSchedule: + """ + Class that models the backup schedule of a Linode instance. + """ + + day: Optional[LinodeInstanceBackupScheduleDay] + window: Optional[LinodeInstanceBackupScheduleWindow] + + +@dataclass +class LinodeInstanceBackups: + """ + Class that models the backup status of a Linode instance. + """ + + available: bool + enabled: bool + schedule: LinodeInstanceBackupSchedule + last_successful: Optional[datetime] = None + + +@dataclass +class LinodeInstance: + """ + Class that models a Linode instance. + """ + + id: int + name: str + instance_type: str + ipv4_addresses: List[str] + ipv6_address: str + group: str + status: LinodeInstanceStatus + tags: List[str] + image: str + region: str + hypervisor: str + specs: LinodeInstanceSpecs + alerts: LinodeInstanceAlerts + backups: LinodeInstanceBackups + created_at: datetime + updated_at: datetime diff --git a/platypush/schemas/linode/_schemas.py b/platypush/schemas/linode/_schemas.py new file mode 100644 index 0000000000..a97b7e2c58 --- /dev/null +++ b/platypush/schemas/linode/_schemas.py @@ -0,0 +1,349 @@ +from typing import Any, Optional + +from marshmallow import fields, pre_load, post_dump, Schema, EXCLUDE +from marshmallow.validate import Range + +from platypush.schemas import DateTime, EnumField + +from ._model import ( + LinodeInstanceBackupScheduleDay, + LinodeInstanceBackupScheduleWindow, + LinodeInstanceStatus, +) + + +class FieldWithId(fields.Function): + """ + Field that handles values that are objects with an ``id`` attribute. + """ + + def _deserialize(self, value: Any, *_, **__) -> Optional[Any]: + return value.id if value is not None else None + + def _serialize(self, value: Any, *_, **__) -> Optional[Any]: + return value + + +class LinodeBaseSchema(Schema): + """ + Base schema for Linode objects. + """ + + class Meta: # type: ignore + """ + Meta class for the schema. + """ + + unknown = EXCLUDE + + +class LinodeInstanceSpecsSchema(LinodeBaseSchema): + """ + Linode instance specifications schema. + """ + + disk = fields.Int( + metadata={ + 'description': 'Allocated disk size, in MB', + 'example': 100000, + } + ) + + memory = fields.Int( + metadata={ + 'description': 'Allocated RAM size, in MB', + 'example': 8192, + } + ) + + cpus = fields.Int( + data_key='vcpus', + metadata={ + 'description': 'Number of virtual CPUs allocated to the instance', + 'example': 4, + }, + ) + + gpus = fields.Int( + metadata={ + 'metadata': { + 'description': 'Number of GPUs allocated to the instance', + 'example': 1, + } + } + ) + + transfer = fields.Int( + metadata={ + 'description': ( + 'Number of network transfers this instance is allotted each month', + ), + 'example': 5000, + } + ) + + @post_dump + def post_dump(self, data: dict, **_) -> dict: + if 'vcpus' in data: + data['cpus'] = data.pop('vcpus') + return data + + +class LinodeInstanceAlertsSchema(LinodeBaseSchema): + """ + Schema that models the alerts configuration of a Linode instance. + """ + + cpu = fields.Int( + allow_none=True, + metadata={ + 'description': ( + 'The percentage of CPU average usage over the past two hours ' + 'required to trigger an alert' + ), + 'example': 90, + }, + ) + + io = fields.Int( + allow_none=True, + metadata={ + 'description': ( + 'The amount of disk I/O operations per second required to ' + 'trigger an alert' + ), + 'example': 5000, + }, + ) + + network_in = fields.Float( + allow_none=True, + metadata={ + 'description': ( + 'The amount of incoming network traffic, in Mbit/s, ' + 'required to trigger an alert' + ), + 'example': 10, + }, + ) + + network_out = fields.Float( + allow_none=True, + metadata={ + 'description': ( + 'The amount of outgoing network traffic, in Mbit/s, ' + 'required to trigger an alert' + ), + 'example': 10, + }, + ) + + transfer_quota = fields.Float( + allow_none=True, + metadata={ + 'validate': Range(min=0, max=100), + 'description': ( + 'The percentage of network transfer that may be used before ' + 'an alert is triggered, between 0 and 100' + ), + 'example': 80, + }, + ) + + +class LinodeInstanceBackupScheduleSchema(LinodeBaseSchema): + """ + Schema that models the backup schedule of a Linode instance. + """ + + day = EnumField( + type=LinodeInstanceBackupScheduleDay, + allow_none=True, + metadata={ + 'description': 'Day of the week when the backups are scheduled', + 'example': 'Sunday', + }, + ) + + window = EnumField( + type=LinodeInstanceBackupScheduleWindow, + allow_none=True, + metadata={ + 'description': 'Time window when the backups are scheduled', + 'example': 'W10', + }, + ) + + +class LinodeInstanceBackupsSchema(LinodeBaseSchema): + """ + Schema that models the backup status of a Linode instance. + """ + + available = fields.Bool( + metadata={ + 'description': 'Whether the backups are available for this instance', + 'example': True, + } + ) + + enabled = fields.Bool( + metadata={ + 'description': 'Whether the backups are enabled on this instance', + 'example': True, + } + ) + + schedule = fields.Nested( + LinodeInstanceBackupScheduleSchema, + metadata={ + 'description': 'Backup schedule configuration', + }, + ) + + last_successful = DateTime( + allow_none=True, + metadata={ + 'description': 'When the last backup was successful', + 'example': '2020-01-01T00:00:00Z', + }, + ) + + +class LinodeInstanceSchema(LinodeBaseSchema): + """ + Class that models a Linode instance. + """ + + id = fields.Int( + required=True, + metadata={ + 'description': 'Instance ID', + 'example': 12345, + }, + ) + + name = fields.String( + required=True, + data_key='label', + metadata={ + 'description': 'Instance name', + 'example': 'my-instance', + }, + ) + + instance_type = FieldWithId( + metadata={ + 'description': 'Instance type', + 'example': 'g6-standard-4', + } + ) + + ipv4_addresses = fields.List( + fields.String(), + data_key='ipv4', + metadata={ + 'description': 'List of IPv4 addresses associated with this instance', + 'example': '["1.2.3.4"]', + }, + ) + + ipv6_address = fields.String( + data_key='ipv6', + metadata={ + 'description': 'IPv6 address associated with this instance', + 'example': '1234:5678::9abc:def0:1234:5678/128', + }, + ) + + group = fields.String( + metadata={ + 'description': 'Group the instance belongs to', + 'example': 'my-group', + } + ) + + status = EnumField( + type=LinodeInstanceStatus, + metadata={ + 'description': 'Instance status', + 'example': 'running', + }, + ) + + tags = fields.List( + fields.String(), + metadata={ + 'description': 'List of tags associated with this instance', + 'example': '["tag1", "tag2"]', + }, + ) + + image = FieldWithId( + metadata={ + 'description': 'Image used to ', + 'example': 'linode/archlinux2014.04', + } + ) + + region = FieldWithId( + metadata={ + 'description': 'Region where the instance is located', + 'example': 'eu-west', + } + ) + + hypervisor = fields.String( + metadata={ + 'description': 'The virtualization engine powering this instance', + 'example': 'kvm', + } + ) + + specs = fields.Nested(LinodeInstanceSpecsSchema) + alerts = fields.Nested(LinodeInstanceAlertsSchema) + backups = fields.Nested(LinodeInstanceBackupsSchema) + created_at = DateTime( + data_key='created', + metadata={ + 'description': 'Instance creation date', + 'example': '2020-01-01T00:00:00Z', + }, + ) + + updated_at = DateTime( + data_key='updated', + metadata={ + 'description': 'When the instance was last polled/updated', + 'example': '2020-01-01T01:00:00Z', + }, + ) + + @pre_load + def pre_load(self, data: dict, **_) -> dict: + from linode_api4.objects.base import MappedObject + + # Expand MappedObjects to dictionaries + for key, value in data.items(): + if isinstance(value, MappedObject): + data[key] = value.dict + + # NOTE Workaround for type -> instance_type not being correctly mapped + if 'type' in data: + data['instance_type'] = data.pop('type') + + return data + + @post_dump + def post_dump(self, data: dict, **_) -> dict: + for data_key, dump_key in [ + ('label', 'name'), + ('ipv4', 'ipv4_addresses'), + ('ipv6', 'ipv6_address'), + ('created', 'created_at'), + ('updated', 'updated_at'), + ]: + if data_key in data: + data[dump_key] = data.pop(data_key) + + return data diff --git a/platypush/utils/mock/modules.py b/platypush/utils/mock/modules.py index 86adf89511..b141441d94 100644 --- a/platypush/utils/mock/modules.py +++ b/platypush/utils/mock/modules.py @@ -66,7 +66,6 @@ mock_imports = [ "keras", "linode_api4", "luma", - "marshmallow_dataclass", "mpd", "ndef", "nfc", diff --git a/requirements.txt b/requirements.txt index 6e6803e86f..28399922fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,6 @@ croniter docutils flask marshmallow -marshmallow_dataclass python-dateutil python-magic pyyaml