Beispiel #1
0
 def addInputNode(self, key, handler, *paramKeys):
     node = Namespace(owner=self.__mpplugs__.key,
                      key=key,
                      paramKeys=list(paramKeys),
                      handler=handler)
     self.executor.inputNodes[key] = node
     self.addEventHandler(f'$_{self.__mpplugs__.key}_{key}', handler)
Beispiel #2
0
 def setPluginOutputs(self, **data):
     if Settings.Kernel.AutoAddTpsToPluginOutputs:
         data['TPS'] = self.executor.tpsMon.tps
     if Settings.Kernel.AutoAddTickToPluginOutputs:
         data['tick'] = self.__mpplugs__.tick
     PluginEvent(self,
                 'PluginStatus',
                 pluginKey=self.__mpplugs__.key,
                 data=Namespace(**data))
Beispiel #3
0
def _addLog(entity, level, *message):
    try:
        data = entity._logger_data
    except AttributeError:
        data = Namespace(type='unknown', path='UNKNOWN')
    levelno = ['debug', 'info', 'note', 'warn', 'error'].index(level)
    for output in Settings.Logger.logOutputs:
        if levelno >= output.minLevel and levelno <= output.maxLevel:
            output.write(_format(data, output, level, *message))
Beispiel #4
0
 def __init__(self, plugin, quitStatus, plgnQueue, evntQueue):
     self.key = plugin.__mpplugs__.key
     self.setIssuerData('plugin', self.key)
     self.tpsMon = TpsMonitor(Settings.Kernel.MaxExecutorTicksPerSec)
     self.quitting = False
     self.initialized = False
     self.programInitDone = False
     self.plugin = plugin
     self.quitStatus = quitStatus
     self.plgnQueue = plgnQueue
     self.evntQueue = evntQueue
     self.plugin.executor = self
     self.inputNodes = Namespace()
     self.evntHandlers = Namespace(Config=[self.configure],
                                   Init=[self.initPlugin],
                                   Quit=[self.quit],
                                   GlobalSettings=[self.setGlobalSettings],
                                   ProgramInitDone=[self.setProgramInit])
     self.criticalExceptions = ['Init', 'Config']
Beispiel #5
0
 def __init__(self, progName=''):
     self.progName = progName
     self.manager = mpr.Manager()
     self.setIssuerData('kernel', 'Program')
     self.tpsMon = TpsMonitor(Settings.Kernel.MaxProgramTicksPerSec)
     self.quitting = False
     self.tick = 0
     self.evntQueue = self.manager.Queue()
     self.plugins = Namespace()
     self.evntHandlers = {
         'AddHandler': [self.addEvtHandler],
         'PluginError': [self.onError],
         'StopProgram': [self.quit],
         'InitDoneState': [self.setInitDoneFlag],
     }
     self.noEvtHandlerWarns = []
     self.compiler = Compiler(self)
     self.phase = 'instance'
     self.settings = {'StartTime': Settings.StartTime}
     self.pluginConfigs = {}
Beispiel #6
0
 def customSettings(self, data):
     if self.phase != 'instance':
         Error(
             self,
             'Settings can not be changed after preload method was called')
         exit()
     Note(self, 'Adding custom entries in program settings')
     try:
         Settings.Custom
     except:
         Settings.Custom = Namespace()
     for key, val in data.items():
         self.settings[f'Custom.{key}'] = val
Beispiel #7
0
    def compilePlugin(self, directory, pluginKey):
        baseDir = f'{directory}/{pluginKey}'
        try:
            next(os.walk(baseDir))
        except:
            raise CompilerNoDirectoryError(baseDir) from None
        scopeFile = f'{baseDir}/Scope.py'
        configFile = f'{baseDir}/Config.py'
        pluginFile = f'{baseDir}/{pluginKey}.py'
        helperFiles = [
            f'{baseDir}/{location}' for location in next(os.walk(baseDir))[2]
            if location[:-3] not in ['Scope', 'Config', pluginKey]
            and location[0] != location[0].lower() and location.endswith('.py')
        ]

        target = f'./{self.target}/{pluginKey}.py'
        ifnmkdir(self.target)
        open(target, 'w+').close()

        allCode = Settings.Compiler.data.compilationPrefix
        sourceFileLines = [['_prefix_', allCode.count('\n')]]
        filesOrdered = [
            ('Scope.py', scopeFile),
            ('Config.py', configFile),
            *[(x.split('/')[-1], x) for x in helperFiles],
            (f'{pluginKey}.py', pluginFile),
        ]
        for name, chunk in filesOrdered:
            try:
                code = open(chunk).read()
                allCode += code
                sourceFileLines += [[name, code.count('\n')]]
            except FileNotFoundError:
                raise CompilerMissingFileError(chunk) from None
        with open(target, 'a', newline='\n') as f:
            f.write(allCode)
        pluginData = Namespace(
            key=pluginKey,
            directory=directory,
            loaded=False,
            sourceFileLines=sourceFileLines,
        )
        try:
            exec(allCode)
        except SyntaxError as exc:
            raise PluginSyntaxError(pluginData, exc) from None
        except Exception as exc:
            raise PluginLoadError(pluginData, exc) from None
        self.prog.plugins[pluginKey] = pluginData
Beispiel #8
0
Settings = Namespace(
    StartTime=datetime.now(),
    Kernel=Namespace(
        MaxProgramTicksPerSec=128,
        MaxExecutorTicksPerSec=128,
        PluginAwaitProgramInit=True,
        MaxPluginCleanupDuration=0.3,
        AutoAddTickToPluginOutputs=False,
        AutoAddTpsToPluginOutputs=False,
    ),
    Compiler=Namespace(
        cacheDirectory='_MpplugsCache',
        pluginDirectories=[],
        omitPlugins=[],
        data=Namespace(compilationPrefix='''
# File compiled by mpplugs, any edits will be automatically overwritten
from mpplugs import Plugin, Settings, Namespace
from mpplugs import PluginEvent as Event
from mpplugs.Logger import Debug, Info, Note, Warn, Error

# Plugin code

'''),
    ),
    Logger=Namespace(
        appendOriginalTraceback=False,
        enablePluginTps=False,
        timeFormat='24h',
        timeMode='absolute',
        timePrefix=Namespace(
            absolute='@',
            relative='T+',
        ),
        logOutputs=[stdout, stderr],
        colors=Namespace(  # level = (timeColor, prefixColor, messageColor)
            debug=('l_magenta', 'l_black', 'l_black'),
            info=('l_magenta', 'l_white', 'l_white'),
            note=('l_cyan', 'l_cyan', 'l_white'),
            warn=('l_yellow', 'l_yellow', 'l_yellow'),
            error=('l_red', 'l_red', 'l_red'),
        ),
    ),
    Text=Namespace(LogIssuerTypes=Namespace(
        kernel='Kernel',
        plugin='Plugin',
    ), ),
    Custom=Namespace(),  # Fill with Program.customSettings
)
Beispiel #9
0
class Program(LogIssuer):
    def __init__(self, progName=''):
        self.progName = progName
        self.manager = mpr.Manager()
        self.setIssuerData('kernel', 'Program')
        self.tpsMon = TpsMonitor(Settings.Kernel.MaxProgramTicksPerSec)
        self.quitting = False
        self.tick = 0
        self.evntQueue = self.manager.Queue()
        self.plugins = Namespace()
        self.evntHandlers = {
            'AddHandler': [self.addEvtHandler],
            'PluginError': [self.onError],
            'StopProgram': [self.quit],
            'InitDoneState': [self.setInitDoneFlag],
        }
        self.noEvtHandlerWarns = []
        self.compiler = Compiler(self)
        self.phase = 'instance'
        self.settings = {'StartTime': Settings.StartTime}
        self.pluginConfigs = {}

    def customSettings(self, data):
        if self.phase != 'instance':
            Error(
                self,
                'Settings can not be changed after preload method was called')
            exit()
        Note(self, 'Adding custom entries in program settings')
        try:
            Settings.Custom
        except:
            Settings.Custom = Namespace()
        for key, val in data.items():
            self.settings[f'Custom.{key}'] = val

    def updateSettings(self, data):
        if self.phase != 'instance':
            Error(
                self,
                'Settings can not be changed after preload method was called')
            exit()
        Note(self, 'Changing settings')
        delKeys = []
        for key, val in data.items():
            try:
                eval(f'Settings.{key}')
                self.settings[key] = val
            except (KeyError, AttributeError):
                if not key.startswith('Custom.'):
                    Warn(self, f'Setting "{key}" does not exist')
                    delKeys += [key]
                    continue
        for key in delKeys:
            del data[key]
        self.settings['StartTime'] = Settings.StartTime

    def configPlugin(self, pluginKey, data):
        if self.phase != 'preloaded':
            Error(
                self,
                'Plugins can be configured only after preload method was called'
            )
            exit()
        self.pluginConfigs[pluginKey] = data

    def preload(self):
        for key, val in self.settings.items():
            if type(val) == str: val = f'"{val}"'
            elif type(val) == datetime:
                val = f"datetime.strptime('{val}', '%Y-%m-%d %H:%M:%S.%f')"
            exec(f'Settings.{key} = {val}')
        try:
            self.compiler.compile()
        except (CompilerError, PluginStartupError) as exc:
            self.onError(KernelExcEvent(True, exc))
            exit()
        self.tpsMon.setTarget(Settings.Kernel.MaxProgramTicksPerSec)
        self.phase = 'preloaded'

    def run(self):
        try:
            self.compiler.load()
        except Exception as exc:
            self.onError(KernelExcEvent(True, exc))
        Note(self, 'Starting program')
        self.phase = 'running'
        while not self.quitting:
            try:
                self.handleAllEvents()
                self.tpsMon.tick()
            except KeyboardInterrupt:
                break
            except:
                self.phase = 'exception'
                raise
        self.quit()

    def handleAllEvents(self):
        while not mh.empty(self.evntQueue):
            event = mh.pop(self.evntQueue)
            try:
                hndPlugins = self.evntHandlers[event.id]
            except KeyError:
                if event.id not in self.noEvtHandlerWarns:
                    Warn(self, f'Event "{event.id}" has no handlers assigned')
                    self.noEvtHandlerWarns.append(event.id)
                continue
            except AttributeError:
                Warn(self, f'There was a boolean in event queue')
                continue
            for handler in hndPlugins:
                if callable(handler):
                    handler(event)  # Execute internal handler
                else:  # Send event to all plugin executors with handlers
                    mh.push(self.plugins[handler].queue, event)
        self.tpsMon.tick()

    def quit(self, event=None):
        # set program flags
        if self.phase == 'quitting' or self.quitting: return
        if self.phase != 'exception': self.phase = 'quitting'
        Note(self, 'Starting cleanup')
        self.quitting = True
        # quit plugins
        for key, plugin in self.plugins.items():
            if not plugin.loaded: continue
            if self.phase == 'exception': mh.set(plugin.quitStatus, 2)
            else:
                mh.set(plugin.quitStatus, 1)
                mh.push(plugin.queue, KernelEvent('Quit'))
        sleep(Settings.Kernel.MaxPluginCleanupDuration)
        for key, plugin in self.plugins.items():
            if not plugin.loaded: continue
            plugin.proc.join()
        # working directory cleanup
        Info(self, 'Deleting temporary files')
        CleanPyCache()
        self.compiler.removeTemp()

    # Methods called by plugins' executors

    def addEvtHandler(self, event):
        try:
            self.evntHandlers[event.eventKey] += [event.plugin]
        except KeyError:
            self.evntHandlers[event.eventKey] = [event.plugin]

    def setInitDoneFlag(self, event):
        plugin = self.plugins[event.pluginKey]
        plugin.initDone = event.state
        plugin.inputNodes = event.nodes
        allDone = True
        allNodes = {}
        for plugin in self.plugins.values():
            if not plugin.initDone:
                allDone = False
                break
            allNodes.update(
                {f'{plugin.key}_{k}': v
                 for k, v in plugin.inputNodes.items()})
        if allDone:
            Note(self, f'All plugins initialized')
            for plugin in self.plugins.values():
                mh.push(plugin.queue,
                        KernelEvent('ProgramInitDone', nodes=allNodes))

    def onError(self, event):
        prefix = ['EventError', 'Critical'][event.critical]
        errorMessage = f'({prefix}) {event.name}: {event.info}'
        if Settings.Logger.appendOriginalTraceback:
            try:
                errorMessage += '\n\nOriginal Traceback:\n' + event.originalTraceback
            except AttributeError:
                pass
        Error(self, errorMessage)
        self.phase = 'exception'
        if event.critical: self.quit()
Beispiel #10
0
 def __init__(self, key):
     self.__mpplugs__ = Namespace(key=key, tick=0)
     self.setIssuerData('plugin', key)
Beispiel #11
0
import sys

from mpplugs.Namespace import Namespace
Levels = Namespace(Debug=0, Info=1, Note=2, Warn=3, Critical=4)


class StreamOutput:
    def __init__(self,
                 stream,
                 colored=False,
                 minLevel=Levels.Debug,
                 maxLevel=Levels.Critical,
                 enabled=True):
        self.stream = stream
        self.colored = colored
        self.minLevel, self.maxLevel = minLevel, maxLevel
        self.enabled = enabled

    def write(self, data):
        self.stream.write(data)
        self.stream.flush()


class FileOutput(StreamOutput):
    def __init__(self,
                 filename,
                 colored=False,
                 minLevel=Levels.Debug,
                 maxLevel=Levels.Critical,
                 enabled=True):
        with open(filename, 'a') as file:
Beispiel #12
0
 def setIssuerData(self, issuerType, entPath):
     self._logger_data = Namespace(type=issuerType, path=entPath)
Beispiel #13
0
class Executor(LogIssuer):
    def __init__(self, plugin, quitStatus, plgnQueue, evntQueue):
        self.key = plugin.__mpplugs__.key
        self.setIssuerData('plugin', self.key)
        self.tpsMon = TpsMonitor(Settings.Kernel.MaxExecutorTicksPerSec)
        self.quitting = False
        self.initialized = False
        self.programInitDone = False
        self.plugin = plugin
        self.quitStatus = quitStatus
        self.plgnQueue = plgnQueue
        self.evntQueue = evntQueue
        self.plugin.executor = self
        self.inputNodes = Namespace()
        self.evntHandlers = Namespace(Config=[self.configure],
                                      Init=[self.initPlugin],
                                      Quit=[self.quit],
                                      GlobalSettings=[self.setGlobalSettings],
                                      ProgramInitDone=[self.setProgramInit])
        self.criticalExceptions = ['Init', 'Config']

    def updateLoop(self):
        while not self.quitting:
            if mh.get(self.quitStatus): break
            while not mh.empty(self.plgnQueue):
                event = mh.pop(self.plgnQueue)
                self.handleEvent(event)
            if self.initialized:
                if self.programInitDone or not Settings.Kernel.PluginAwaitProgramInit:
                    self.tickPlugin()
            self.tpsMon.tick()

    def handleEvent(self, event):
        try:
            handlers = self.evntHandlers[event.id]
        except KeyError:
            Warn(self, f'Unhandled event "{event.id}"')
            return
        for handler in handlers:
            try:
                handler(event)
            except (PluginStartupError, PluginRuntimeError) as exc:
                ExecutorExcEvent(self, True, exc)
                self.quitting = True
            except Exception as exc:
                ExecutorExcEvent(self, False,
                                 PluginEventError(self.plugin, event.id, exc))

    def tickPlugin(self):
        if Settings.Logger.enablePluginTps:
            if self.tpsMon.newTpsReading:
                Debug(self, f'TPS = {self.tpsMon.tps}')
        try:
            self.plugin.update()
        except Exception as exc:
            ExecutorExcEvent(self, True, PluginTickError(self.plugin, exc))

    def quitProgram(self):
        ExecutorEvent(self, 'StopProgram')
        self.quit()

    # Internal event handlers

    def initPlugin(self, event):
        Info(self, f'Plugin init starts')
        try:
            self.plugin.init()
            self.initialized = True
        except Exception as exc:
            raise PluginInitError(self.plugin, exc)
        bareNodes = {
            key: {k: v
                  for k, v in node.items() if k != 'handler'}
            for key, node in self.inputNodes.items()
        }
        ExecutorEvent(self,
                      'InitDoneState',
                      pluginKey=self.key,
                      state=True,
                      nodes=bareNodes)
        Info(self, f'Plugin init done')

    def configure(self, event):
        for path, value in event.data.items():
            try:
                eval(f'self.plugin.cnf.{path}')
            except AttributeError as exc:
                raise PluginConfigError(self.plugin, path, exc)
            if type(value) == str: value = f'"{value}"'
            exec(f'self.plugin.cnf.{path} = {value}')

    def setGlobalSettings(self, event):
        data = event.data
        for key, val in data.items():
            if type(val) == str: val = f'"{val}"'
            elif type(val) == datetime:
                val = f"datetime.strptime('{val}', '%Y-%m-%d %H:%M:%S.%f')"
            exec(f'Settings.{key} = {val}')
        self.tpsMon.setTarget(Settings.Kernel.MaxExecutorTicksPerSec)

    def setProgramInit(self, event):
        self.programInitDone = True
        try:
            pluginHandler = self.plugin.onProgramInit
            Info(self, 'Executing on-program-init method')
        except AttributeError:
            return
        pluginHandler(event)

    def quit(self, event=None):
        self.quitting = True
        self.plugin.quit()