Browse Source

V2.0 plugin support

Steve Nyemba 1 year ago
parent
commit
383f887db6
1 changed files with 128 additions and 0 deletions
  1. 128 0
      transport/plugins/__init__.py

+ 128 - 0
transport/plugins/__init__.py

@@ -0,0 +1,128 @@
+"""
+The functions within are designed to load external files and apply functions against the data
+The plugins are applied as 
+    - post-processing if we are reading data 
+    - and pre-processing if we are writing data
+
+The plugin will use a decorator to identify meaningful functions
+@TODO: This should work in tandem with loggin (otherwise we don't have visibility into what is going on)
+"""
+import importlib as IL
+import importlib.util
+import sys
+import os
+
+class plugin :
+    """
+    Implementing function decorator for data-transport plugins (post-pre)-processing
+    """
+    def __init__(self,**_args):
+        """
+        :name   name of the plugin
+        :mode   restrict to reader/writer
+        :about  tell what the function is about    
+        """
+        self._name = _args['name']
+        self._about = _args['about']
+        self._mode = _args['mode'] if 'mode' in _args else 'rw'
+    def __call__(self,pointer):
+        def wrapper(_args):
+            return pointer(_args)
+        #
+        # @TODO:
+        # add attributes to the wrapper object
+        #
+        setattr(wrapper,'transport',True)
+        setattr(wrapper,'name',self._name)
+        setattr(wrapper,'mode',self._mode)
+        setattr(wrapper,'about',self._about)
+        return wrapper
+
+
+class PluginLoader :
+    """
+    This class is intended to load a plugin and make it available and assess the quality of the developed plugin
+    """
+    def __init__(self,**_args):
+        """
+        :path   location of the plugin (should be a single file)
+        :_names of functions to load
+        """
+        _names = _args['names'] if 'names' in _args else None
+        path = _args['path'] if 'path' in _args else None
+        self._names = _names if type(_names) == list else [_names]
+        self._modules = {}
+        self._names = []
+        if path and os.path.exists(path) and _names:
+            for _name in self._names :
+                spec = importlib.util.spec_from_file_location('private', path)
+                module = importlib.util.module_from_spec(spec)
+                spec.loader.exec_module(module) #--loads it into sys.modules
+                if hasattr(module,_name) :
+                    if self.isplugin(module,_name) :
+                        self._modules[_name] = getattr(module,_name)
+                    else:
+                        print ([f'Found {_name}', 'not plugin'])
+                else:
+                    #
+                    # @TODO: We should log this somewhere some how
+                    print (['skipping ',_name, hasattr(module,_name)])
+                    pass
+        else:
+            #
+            # Initialization is empty
+            self._names = []
+        pass
+    def set(self,_pointer) :
+        """
+        This function will set a pointer to the list of modules to be called
+        This should be used within the context of using the framework as a library
+        """
+        _name = _pointer.__name__
+        
+        self._modules[_name] = _pointer
+        self._names.append(_name)
+    def isplugin(self,module,name):
+        """
+        This function determines if a module is a recognized plugin
+        :module     module object loaded from importlib
+        :name       name of the functiion of interest
+        """
+        
+        p = type(getattr(module,name)).__name__ =='function'
+        q = hasattr(getattr(module,name),'transport')
+        #
+        # @TODO: add a generated key, and more indepth validation
+        return p and q
+    def has(self,_name):
+        """
+        This will determine if the module name is loaded or not
+        """
+        return _name in self._modules 
+    def ratio (self):
+        """
+        how many modules loaded vs unloaded given the list of names
+        """
+
+        _n = len(self._names)
+        return len(set(self._modules.keys()) & set (self._names)) / _n
+    def apply(self,_data):
+        for _name in self._modules :
+            _pointer = self._modules[_name]
+            #
+            # @TODO: add exception handling
+            _data = _pointer(_data)
+        return _data
+    # def apply(self,_data,_name):
+    #     """
+    #     This function applies an external module function against the data.
+    #     The responsibility is on the plugin to properly return data, thus responsibility is offloaded
+    #     """
+    #     try:
+            
+    #         _pointer = self._modules[_name]
+    #         _data = _pointer(_data)
+
+    #     except Exception as e:
+    #         pass
+    #     return _data