Added basic skeleton
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Pünkösd Marcell 2021-04-08 23:52:26 +02:00
parent 43f80a79c1
commit 769ee0aeca
7 changed files with 244 additions and 1 deletions

View File

@ -0,0 +1,6 @@
import os
# Config is loaded statically at import time
class Config:
pass

View File

@ -1,5 +1,34 @@
import os
import sys
from config import Config
from plugins import WaitPlugin
from plugin_repository import PluginRepository
from program_executor import ProgramExecutor
import logging
def main():
print("Hello world!")
# init
logging.basicConfig(
stream=sys.stdout,
format="%(asctime)s [%(levelname)s]: %(name)s: %(message)s",
level=logging.DEBUG
)
compiler_repo = PluginRepository()
compiler_repo.register_plugin(WaitPlugin)
# Example code:
compiler_repo.load_plugin("wait")
program = []
program.append(compiler_repo.get_compiler("wait").compile(secs=2))
# execute:
executor = ProgramExecutor(program)
executor.start()
# End of execution
executor.join()
compiler_repo.close()
if __name__ == '__main__':

View File

@ -0,0 +1,35 @@
from typing import Dict, List
from plugins import AbstractPlugin, AbstractCommandCompiler
import logging
class PluginRepository:
def __init__(self):
self._registered_plugins: Dict[str, type(AbstractPlugin)] = {}
self._loaded_plugins: List[AbstractPlugin] = []
self._command_compilers: Dict[str, AbstractCommandCompiler] = {}
self._logger = logging.getLogger("plugin_repository")
def register_plugin(self, plugin_class: type(AbstractPlugin)):
self._registered_plugins[plugin_class.plugin_name] = plugin_class
self._logger.debug(f"Registered plugin: {plugin_class.plugin_name}")
def load_plugin(self, plugin: str):
plugin_instance = self._registered_plugins[plugin]() # config is statically loaded
self._loaded_plugins.append(plugin_instance)
compilers = plugin_instance.load_compilers()
self._command_compilers.update(compilers)
self._logger.info(f"Loaded plugin: {plugin}")
self._logger.debug(f"Plugin {plugin} loaded the following commands: {', '.join(compilers.keys())}")
def get_compiler(self, command: str) -> AbstractCommandCompiler:
return self._command_compilers[command]
def close(self):
self._command_compilers = []
for plugin in self._loaded_plugins:
plugin.close()
self._logger.info(f"Unloaded plugin: {plugin.plugin_name}")

View File

@ -0,0 +1,2 @@
from .abstract_plugin import AbstractCommand, AbstractCommandCompiler, AbstractPlugin
from .wait_plugin import WaitPlugin

View File

@ -0,0 +1,32 @@
from typing import Dict
from abc import ABC, abstractmethod
class AbstractCommand(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def describe(self) -> dict:
pass
class AbstractCommandCompiler(ABC):
@abstractmethod
def compile(self, *args, **kwargs) -> AbstractCommand:
pass
class AbstractPlugin(ABC):
plugin_name = ""
@abstractmethod
def load_compilers(self) -> Dict[str, AbstractCommandCompiler]:
pass
@abstractmethod
def close(self):
pass

View File

@ -0,0 +1,53 @@
from typing import Dict
from .abstract_plugin import AbstractCommand, AbstractPlugin, AbstractCommandCompiler
import logging
import time
class WaitCommand(AbstractCommand):
def __init__(self, logger: logging.Logger, secs: float):
if type(secs) not in [float, int]:
raise ValueError("Secs must be float or int")
if secs <= 0:
raise ValueError("Secs must be a positive integer")
self._secs = secs
self._logger = logger
def execute(self):
self._logger.debug(f"Sleeping for {self._secs} seconds")
time.sleep(self._secs)
self._logger.debug(f"Slept for {self._secs} seconds")
def describe(self) -> dict:
return {
"secs": self._secs
}
class WaitCompiler(AbstractCommandCompiler):
def __init__(self, logger: logging.Logger):
self._logger = logger
def compile(self, secs: float) -> AbstractCommand:
return WaitCommand(self._logger, secs)
class WaitPlugin(AbstractPlugin):
plugin_name = "wait"
def __init__(self):
self._logger = logging.getLogger("plugin").getChild("wait")
def load_compilers(self) -> Dict[str, AbstractCommandCompiler]:
return {
"wait": WaitCompiler(self._logger)
}
def close(self):
pass

View File

@ -0,0 +1,86 @@
from typing import List
from threading import Thread
from plugins import AbstractCommand
from enum import Enum
import logging
class ProgramExecutorStates(Enum):
PREPARING = 0
RUNNING = 1
DONE = 2
ABORTED = 3
CRASHED = 4
class ProgramExecutor(Thread):
def __init__(
self,
program: List[AbstractCommand],
loop: bool = False
):
super().__init__()
self._program = program
self._loop = loop
self._current_step_desc = {}
self._pc = 0
self._loop_counter = 0
# TODO: jogging wait
self._state = ProgramExecutorStates.PREPARING
self._logger = logging.getLogger("program_executor")
def abort(self):
self._state = ProgramExecutorStates.ABORTED
# TODO: implement abort
def cont(self):
# TODO: jogging wait
pass
def get_status(self) -> dict:
return {
"current_step": self._pc,
"program_length": len(self._program),
"step_description": self._current_step_desc,
"state": self._state.name,
"loop_count": self._loop_counter,
"config": {
"loop": self._loop
}
}
@property
def state(self):
return self._state
def run(self) -> None:
self._state = ProgramExecutorStates.RUNNING
self._logger.info("Start running program")
while True: # needed for loop
self._loop_counter += 1
for i, step in enumerate(self._program):
self._pc = i
self._current_step_desc = step.describe()
self._logger.debug(f"Executing step {self._pc}")
try:
step.execute()
except Exception as e:
self._logger.exception(e)
self._state = ProgramExecutorStates.CRASHED
return
# TODO: jogging wait
if self._loop:
self._logger.debug("Looping program")
else:
self._logger.debug("Program ended")
break
self._state = ProgramExecutorStates.DONE