Package: libkplug
libkpluglibkplug is a simple framework for creating modular, configuration driven python applications.Q: How is this different from just standard module development.A: libkplug provides mechanisms to import and instantiate instances of classes by string identifiers, allowing for
development of applications that can have behaviors altered or extended through config change alone.Q: What are some example use-cases?A1: Say I have developed a automation suite, which uses YAML files as a input source, with libkplug, I could abstract the
YAML file loader into a named "ConfigReaderPlugin", and ship my automation suite with ConfigReaderPlugin set to the YAML
file loader class 'YAMLFileLoader'. A user could then implement their own "ConfigReaderPlugin" which integrates with GIT,
with the same contract as 'YAMLFileReader', and by allowing the user to set a variable 'ConfigPlugin=GitConfigLoader',
the user has the means to integrate the application with ease.Q: How do I instantiate plugins without having the type imported before-hand?A: Code# read the class of a pluginclazz=libkplug.plugin_registry('SomePluginName')# instantiate with args and kwargs as usualmyinst=clazz(key1=1,keyN='N')Installationsource/my/venv/bin/activate
pythonsetup.pyinstallUsageSimple ExampleThis is a simple example, which will load the moduleplugins.plugin_helloworld,
which contains a pluginHelloWorldPlugin. The program then instantiates a instance via the registry, using the plugin
name as an argument.# import libkplugimportlibkplugfromlibksettingsimportKSettings# initialize settings with some defaults, suppressing config.yaml file loadingsettings=KSettings(MY_HELLO_WORLD_CLASS='HelloWorldPlugin',PLUGINS=['plugins.plugin_helloworld'],load_yaml=False)# get the class of the pluginclazz=libkplug.plugin_registry(settings.MY_HELLO_WORLD_CLASS)# instantiate it passing in args and kwargs if neededinst=clazz(True,"Foo",kwarg1=1,kwargN="N")# keep the program in a loopwhileTrue:passThe output$>pythonrun.py__init__.py:157INFOInitializingpluginarchitecture
__init__.py:67INFOPluginInit:(),{}__init__.py:55INFOLoadingyamlfile:config.yaml
__init__.py:61INFOSettingdefaultMY_HELLO_WORLD_CLASS=HelloWorldPlugin
__init__.py:61INFOSettingdefaultPLUGINS=['plugins.plugin_helloworld']__init__.py:71INFOSettingconfigKEY1=VAL1
__init__.py:71INFOSettingconfigPLUGINS=['plugins.plugin_helloworld']__init__.py:71INFOSettingconfigMY_HELLO_WORLD_CLASS=HelloWorldPlugin
__init__.py:77INFOYamlloadedsuccessful
__init__.py:83INFOPluginstoload:['plugins.plugin_helloworld']__init__.py:86INFOAttemptingtoloadplugin:plugins.plugin_helloworld
__init__.py:80INFORegisteringPluginid:HelloWorldPluginfromclass:<class'plugins.plugin_helloworld.HelloWorldPlugin'>run.py:22INFOStartingup
__init__.py:117INFOOrderedDict([('HelloWorldPlugin',<class'plugins.plugin_helloworld.HelloWorldPlugin'>)])__init__.py:118INFOModuleUnknown->run.py:25->HelloWorldPlugin
__init__.py:119INFOPluginRequest:args:('HelloWorldPlugin',),kwargs:{}__init__.py:121INFOClass:<class'plugins.plugin_helloworld.HelloWorldPlugin'>
plugin_helloworld.py:16INFOInstantiatinginstanceof:HelloWorldPluginargs:()andkwargs:{'settings':<libksettings.KSettingsobjectat0x1094f1358>}plugin_helloworld.py:38INFOThread-1:SatJan1909:35:242019plugin_helloworld.py:38INFOThread-2:SatJan1909:35:262019plugin_helloworld.py:38INFOThread-1:SatJan1909:35:262019Sample ApplicationSeeexamplewhich loads config.yaml file, and starts a plugin which has two threads.libkpluglibkplug provides the KPlugin base class, plugin_registry, and plugin registration decorators.Simple example plugin:importlogging# initializes the plugin systemimportlibkplug# a inline plugin registration@libkplug.plugin_registry.registerclassHelloWorldPlugin(libkplug.KPlugin):# this is the identifier for when calling the plugin, should almost always be the class nameplugin_name='HelloWorldPlugin'def__init__(self,*args,**kwargs):logging.info('Instantiating instance of:%sargs:%sand kwargs:%s'%(self.plugin_name,args,kwargs))defhello(self,name="default"):logging.info("Hello%sfrom%sinstance method"%(name,self))returnname@staticmethoddefhello_world():logging.info("Hello World")return"Hello World"Initialize the Plugin System>>># init libkplug>>>importlibkplug>>># check current plugins>>>print(libkplug.plugin_registry.plugins_dict)OrderedDict()>>>importplugins.plugin_helloworkd>>>print(libkplug.plugin_registry.plugins_dict)OrderedDict([('HelloWorldPlugin',<class'plugins.plugin_helloworld.HelloWorldPlugin'>)])Register a Plugin with KSettings>>>importlibksettings>>>importlibkplug__init__.py:157INFOInitializingpluginarchitecture__init__.py:67INFOPluginInit:(),{}>>>settings=libksettings.KSettings(PLUGINS=['plugins.plugin_helloworld'],load_yaml=False)__init__.py:56INFOSettingdefaultPLUGINS=['plugins.plugin_helloworld']__init__.py:87INFOYAMLloadingdisabled__init__.py:96INFOPluginstoload:['plugins.plugin_helloworld']__init__.py:99INFOAttemptingtoloadplugin:plugins.plugin_helloworld__init__.py:80INFORegisteringPluginid:HelloWorldPluginfromclass:<class'plugins.plugin_helloworld.HelloWorldPlugin'>__init__.py:111INFOConfig:{'PLUGINS':['plugins.plugin_helloworld']}>>>Register a Plugin ManuallyImporting any file containing classes decorated with@libkplug.plugin_registry.registerwill register the plugins. Just
be sure to import libkplug before.>>>importlibkplug__init__.py:157INFOInitializingpluginarchitecture__init__.py:67INFOPluginInit:(),{}>>>importplugins.plugin_helloworld__init__.py:80INFORegisteringPluginid:HelloWorldPluginfromclass:<class'plugins.plugin_helloworld.HelloWorldPlugin'>>>>Plugin Static MethodsStatic methods work as expected, and do not require instantiation of the plugin, it only needs to be loaded to work.>>>importlibkplug>>>importplugins.plugin_helloworld>>>libkplug.plugin_registry('HelloWorldPlugin').hello_world()'Hello World'Plugin InstantiationInstantiation is done by determining the class, and then passing in kwargs to the constructor as usual.>>>importlibkplug>>>importplugins.plugin_helloworld>>># read the class>>>clazz=libkplug.plugin_registry('HelloWorldPlugin')>>># instantiate with kwargs>>>myinst=clazz(key1=1,keyN='N')libksettingsKSettings is designed to load yaml files, set defaults, and initialize plugins listed inPLUGINS.IMPORTANT: KSettings is "global", so once instantiated with "defaults" and or a config, any other part of the application
can import KSettings and access previously set values without reloading the config or redefining defaults. That said you
might want to use namespaced defaults if plugins need to declare defaults.Example of separate instances "sharing" config>>>importlibksettings>>>settings1=libksettings.KSettings(A='a',b=1,c=True,PLUGINS=[],load_yaml=False)>>>settings2=libksettings.KSettings(load_yaml=False)>>>settings1.A==settings2.ATrueExample if separate instances declaring new defaults>>>importlibksettings>>>foo_settings=libksettings.KSettings(A='a',b=1,c=True,PLUGINS=[],load_yaml=False,FOO_DEFAULT="foo")>>>bar_settings=libksettings.KSettings(A='a',b=1,c=True,PLUGINS=[],load_yaml=False,BAR_DEFAULT="bar")>>>foo_settings.BAR_DEFAULT'bar'When KSettings is instantiated, it will use all kwargs as 'default' initial values, then load the yaml file, overriding
any defaults, finally it loads the Plugins, and returns a instance of itself.Example initializing without config file, setting some defaults, and not loading plugins,>>>fromlibksettingsimportKSettings>>>settings=KSettings(A='a',b=1,c=True,PLUGINS=[],load_yaml=False)>>>settings.A'boo'>>>settings.B1Example initializing with DEFAULTS and named config file, and default plugins>>>fromlibksettingsimportKSettings>>>settings=KSettings(config_filename='/tmp/config.yaml'A='a',b=1,c=True,PLUGINS=['plugins.plugin_helloworld'])>>>settings.A'boo'>>>settings.B1>>>settings.SOME_YAML_KEY'bar'Example initializing with DEFAULTS and named config file from env var>>>importos>>>fromlibksettingsimportKSettings>>>os.environ.setdefault('MY_CONF','/tmp/other.yaml')>>>settings=KSettings(config_filename_envvar='MY_CONF'A='a',b=1,c=True,PLUGINS=['plugins.plugin_helloworld'])>>>settings.A'boo'>>>settings.B1Example initializing with config search locations>>>importos>>>fromlibksettingsimportKSettings>>>os.environ.setdefault('MY_CONF','/tmp/other.yaml')>>>settings=KSettings(config_filename='myconf.ysml',config_load_locations=["/opt/mysvc","/etc/mysvc"],A='a',b=1,c=True,PLUGINS=['plugins.plugin_helloworld'])>>>settings.A'boo'>>>settings.B1Licenselibkplug,apluginsystemforpythonapplicationsCopyright(C)2013KeganHoltzhausenThisprogramisfreesoftware:youcanredistributeitand/ormodifyitunderthetermsoftheGNUGeneralPublicLicenseaspublishedbytheFreeSoftwareFoundation,eitherversion3oftheLicense,or(atyouroption)anylaterversion.Thisprogramisdistributedinthehopethatitwillbeuseful,butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyofMERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.SeetheGNUGeneralPublicLicenseformoredetails.YoushouldhavereceivedacopyoftheGNUGeneralPublicLicensealongwiththisprogram.Ifnot,see<http://www.gnu.org/licenses/>.