From 99909c73ab47f9c95cb773a93fc4766664404d1f Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sun, 25 Aug 2024 16:06:56 +0200 Subject: [PATCH] [#341] Backend implementation of the new `procedure` entities architecture. --- platypush/config/__init__.py | 1 + platypush/entities/_engine/_procedure.py | 7 -- platypush/entities/managers/procedures.py | 29 ++++++ platypush/entities/procedures.py | 8 +- platypush/plugins/inspect/__init__.py | 2 +- platypush/plugins/procedures/__init__.py | 94 +++++++++++++++++++ .../{inspect => procedures}/_serialize.py | 0 platypush/plugins/procedures/manifest.json | 7 ++ 8 files changed, 138 insertions(+), 10 deletions(-) delete mode 100644 platypush/entities/_engine/_procedure.py create mode 100644 platypush/entities/managers/procedures.py create mode 100644 platypush/plugins/procedures/__init__.py rename platypush/plugins/{inspect => procedures}/_serialize.py (100%) create mode 100644 platypush/plugins/procedures/manifest.json diff --git a/platypush/config/__init__.py b/platypush/config/__init__.py index 244c799af4..945a47af85 100644 --- a/platypush/config/__init__.py +++ b/platypush/config/__init__.py @@ -272,6 +272,7 @@ class Config: @property def _core_plugins(self) -> Dict[str, dict]: return { + 'procedures': {}, 'variable': {}, } diff --git a/platypush/entities/_engine/_procedure.py b/platypush/entities/_engine/_procedure.py deleted file mode 100644 index 74b566f236..0000000000 --- a/platypush/entities/_engine/_procedure.py +++ /dev/null @@ -1,7 +0,0 @@ -class ProceduresManager: - """ - This class is responsible for managing the procedures as native entities. - """ - - def __init__(self): - self.procedures = {} diff --git a/platypush/entities/managers/procedures.py b/platypush/entities/managers/procedures.py new file mode 100644 index 0000000000..50335c9a47 --- /dev/null +++ b/platypush/entities/managers/procedures.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +from typing import Callable, Dict, Union + +from platypush.config import Config +from . import EntityManager + + +class ProcedureEntityManager(EntityManager, ABC): + """ + Base class for integrations that can run and manage procedures. + """ + + @abstractmethod + def exec(self, procedure: str, *args, **kwargs): + """ + Run a procedure. + + :param procedure: Procedure to run, by name. + :param args: Arguments to pass to the procedure. + :param kwargs: Keyword arguments to pass to the procedure. + """ + raise NotImplementedError() + + @property + def _all_procedures(self) -> Dict[str, Union[dict, Callable]]: + """ + :return: All the procedures that can be run by this entity manager. + """ + return Config.get_procedures() diff --git a/platypush/entities/procedures.py b/platypush/entities/procedures.py index 8cdebba4eb..3609542fef 100644 --- a/platypush/entities/procedures.py +++ b/platypush/entities/procedures.py @@ -28,9 +28,13 @@ if not is_defined('procedure'): id = Column( Integer, ForeignKey('entity.id', ondelete='CASCADE'), primary_key=True ) - name = Column(String, unique=True, nullable=False) args = Column(JSON, nullable=False, default=[]) - type = Column(Enum('python', 'config', name='procedure_type'), nullable=False) + procedure_type = Column( + Enum('python', 'config', name='procedure_type'), nullable=False + ) + module = Column(String) + source = Column(String) + line = Column(Integer) __table_args__ = {'keep_existing': True} __mapper_args__ = { diff --git a/platypush/plugins/inspect/__init__.py b/platypush/plugins/inspect/__init__.py index 1449aab343..15667f3f25 100644 --- a/platypush/plugins/inspect/__init__.py +++ b/platypush/plugins/inspect/__init__.py @@ -12,6 +12,7 @@ from platypush.common.db import override_definitions from platypush.common.reflection import Integration, Message as MessageMetadata from platypush.config import Config from platypush.plugins import Plugin, action +from platypush.plugins.procedure import ProcedureEncoder from platypush.message import Message from platypush.message.event import Event from platypush.message.response import Response @@ -20,7 +21,6 @@ from platypush.utils.mock import auto_mocks from platypush.utils.manifest import Manifest, Manifests, PackageManagers from ._cache import Cache -from ._serialize import ProcedureEncoder class InspectPlugin(Plugin): diff --git a/platypush/plugins/procedures/__init__.py b/platypush/plugins/procedures/__init__.py new file mode 100644 index 0000000000..5c5813e724 --- /dev/null +++ b/platypush/plugins/procedures/__init__.py @@ -0,0 +1,94 @@ +import json +from dataclasses import dataclass +from typing import Callable, Collection, Optional, Union + +from platypush.entities.managers.procedures import ProcedureEntityManager +from platypush.entities.procedures import Procedure +from platypush.plugins import RunnablePlugin, action +from platypush.utils import run + +from ._serialize import ProcedureEncoder + + +@dataclass +class _ProcedureWrapper: + name: str + obj: Union[dict, Callable] + + +class ProceduresPlugin(RunnablePlugin, ProcedureEntityManager): + """ + Utility plugin to run and store procedures as native entities. + """ + + @action + def exec(self, procedure: str, *args, **kwargs): + return run(f'procedure.{procedure}', *args, **kwargs) + + def _convert_procedure(self, name: str, proc: Union[dict, Callable]) -> Procedure: + metadata = self._serialize_procedure(proc, name=name) + return Procedure( + id=name, + name=name, + plugin=self, + procedure_type=metadata['type'], + module=metadata.get('module'), + source=metadata.get('source'), + line=metadata.get('line'), + args=metadata.get('args', []), + ) + + @action + def status(self, *_, **__): + """ + :return: The serialized configured procedures. Format: + + .. code-block:: json + + { + "procedure_name": { + "type": "python", + "module": "module_name", + "source": "/path/to/source.py", + "line": 42, + "args": ["arg1", "arg2"] + } + } + + """ + self.publish_entities(self._get_wrapped_procedures()) + return self._get_serialized_procedures() + + def transform_entities( + self, entities: Collection[_ProcedureWrapper], **_ + ) -> Collection[Procedure]: + return [ + self._convert_procedure(name=proc.name, proc=proc.obj) for proc in entities + ] + + def _get_wrapped_procedures(self) -> Collection[_ProcedureWrapper]: + return [ + _ProcedureWrapper(name=name, obj=proc) + for name, proc in self._all_procedures.items() + ] + + @staticmethod + def _serialize_procedure( + proc: Union[dict, Callable], name: Optional[str] = None + ) -> dict: + ret = json.loads(json.dumps(proc, cls=ProcedureEncoder)) + if name: + ret['name'] = name + + return ret + + def _get_serialized_procedures(self) -> dict: + return { + name: self._serialize_procedure(proc, name=name) + for name, proc in self._all_procedures.items() + } + + def main(self, *_, **__): + while not self.should_stop(): + self.publish_entities(self._get_wrapped_procedures()) + self.wait_stop() diff --git a/platypush/plugins/inspect/_serialize.py b/platypush/plugins/procedures/_serialize.py similarity index 100% rename from platypush/plugins/inspect/_serialize.py rename to platypush/plugins/procedures/_serialize.py diff --git a/platypush/plugins/procedures/manifest.json b/platypush/plugins/procedures/manifest.json new file mode 100644 index 0000000000..b489ae96d2 --- /dev/null +++ b/platypush/plugins/procedures/manifest.json @@ -0,0 +1,7 @@ +{ + "manifest": { + "package": "platypush.plugins.procedure", + "type": "plugin", + "events": [] + } +}