1
0
Fork 0
mirror of https://github.com/yweber/lodel2.git synced 2026-03-23 19:42:02 +01:00

Commit with comments, code clean etc.

This commit is contained in:
Yann 2016-04-12 09:20:16 +02:00
commit c0c5d23adb
12 changed files with 236 additions and 45 deletions

14
lodel/plugin/__init__.py Normal file
View file

@ -0,0 +1,14 @@
#-*- coding: utf-8 -*-
## @page howto_writeplugin Write a plugin howto
#
# @section howto_writeplugin_basicstruct Plugin basic structure
# A plugins is a python package that have to contains 3 files :
# - <code>__init__.py</code>
# - <code>main.py</code> ( defined in @ref lodel.plugin.plugins.MAIN_NAME )
# - <code>confspec.py</code> ( defined in @ref lodel.plugin.plugins.CONFSPEC_NAME )
#
# 
from .hooks import LodelHook
from .plugins import Plugins

85
lodel/plugin/hooks.py Normal file
View file

@ -0,0 +1,85 @@
#-*- coding: utf-8 -*-
import os
import copy
from importlib.machinery import SourceFileLoader
## @brief Class designed to handle a hook's callback with a priority
class DecoratedWrapper(object):
## @brief Constructor
# @param hook function : the function to wrap
# @param priority int : the callbacl priority
def __init__(self, hook, priority):
self._priority = priority
self._hook = hook
## @brief Call the callback
# @param hook_name str : The name of the called hook
# @param caller * : The caller (depends on the hook)
# @param payload * : Datas that depends on the hook
# @return modified payload
def __call__(self, hook_name, caller, payload):
return self._hook(hook_name, caller, payload)
## @brief Decorator designed to register hook's callbacks
#
# @note Decorated functions are expected to take 3 arguments :
#  - hook_name : the called hook name
# - caller : the hook caller (depends on the hook)
# - payload : datas depending on the hook
class LodelHook(object):
## @brief Stores all hooks (DecoratedWrapper instances)
_hooks = dict()
## @brief Decorator constructor
# @param hook_name str : the name of the hook to register to
# @param priority int : the hook priority
def __init__(self, hook_name, priority = None):
self._hook_name = hook_name
self._priority = 0xFFFF if priority is None else priority
## @brief called just after __init__
# @param hook function : the decorated function
# @return the hook argument
def __call__(self, hook):
if not self._hook_name in self._hooks:
self._hooks[self._hook_name] = list()
wrapped = DecoratedWrapper(hook, self._priority)
self._hooks[self._hook_name].append(wrapped)
self._hooks[self._hook_name] = sorted(self._hooks[self._hook_name], key = lambda h: h._priority)
return hook
## @brief Call hooks
# @param hook_name str : the hook's name
# @param caller * : the hook caller (depends on the hook)
# @param payload * : datas for the hook
# @param cls
# @return modified payload
@classmethod
def call_hook(cls, hook_name, caller, payload):
if hook_name in cls._hooks:
for hook in cls._hooks[hook_name]:
payload = hook(hook_name, caller, payload)
return payload
## @brief Fetch registered hooks
# @param names list | None : optionnal filter on name
# @param cls
# @return a list of functions
@classmethod
def hook_list(cls, names = None):
res = None
if names is not None:
res = { name: copy.copy(cls._hooks[name]) for name in cls._hooks if name in names}
else:
res = copy.copy(cls._hooks)
return { name: [(hook._hook, hook._priority) for hook in hooks] for name, hooks in res.items() }
## @brief Unregister all hooks
# @param cls
# @warning REALLY NOT a good idea !
# @note implemented for testing purpose
@classmethod
def __reset__(cls):
cls._hooks = dict()

View file

@ -12,8 +12,11 @@ from importlib.machinery import SourceFileLoader, SourcelessFileLoader
# - main.py containing hooks registration etc
# - confspec.py containing a configuration specification dictionary named CONFSPEC
## @brief The package in wich we will load plugins modules
VIRTUAL_PACKAGE_NAME = 'lodel.plugins_pkg'
CONFSPEC_NAME = 'confspec.py'
CONFSPEC_FILENAME = 'confspec.py'
MAIN_FILENAME = 'main.py'
CONFSPEC_VARNAME = 'CONFSPEC'
class PluginError(Exception):
pass
@ -25,7 +28,7 @@ class Plugins(object):
## @brief Optimisation cache storage for plugin paths
_plugin_paths = dict()
def __init__(self):
def __init__(self): # may be useless
self.started()
## @brief Given a plugin name returns the plugin path
@ -59,14 +62,30 @@ class Plugins(object):
plugin_name)
conf_spec_module = plugin_module + '.confspec'
conf_spec_source = plugin_path + CONFSPEC_NAME
conf_spec_source = plugin_path + CONFSPEC_FILENAME
try:
loader = SourceFileLoader(conf_spec_module, conf_spec_source)
confspec_module = loader.load_module()
except ImportError:
raise PluginError("Failed to load plugin '%s'. It seems that the plugin name is not valid" % plugin_name)
return getattr(confspec_module, 'CONFSPEC')
return getattr(confspec_module, CONFSPEC_VARNAME)
## @brief Load a module to register plugin's hooks
# @param plugin_name str : The plugin name
@classmethod
def load_plugin(cls, plugin_name):
cls.started()
plugin_path = cls.plugin_path(plugin_name)
plugin_module = '%s.%s' % ( VIRTUAL_PACKAGE_NAME,
plugin_name)
main_module = plugin_module + '.main'
main_source = plugin_path + MAIN_FILENAME
try:
loader = SourceFileLoader(main_module, main_source)
main_module = loader.load_module()
except ImportError:
raise PluginError("Failed to load plugin '%s'. It seems that the plugin name is not valid" % plugin_name)
## @brief Bootstrap the Plugins class
@classmethod
def bootstrap(cls, plugins_directories):

View file

@ -6,7 +6,7 @@ import configparser
import copy
from collections import namedtuple
from lodel.plugins import Plugins, PluginError
from lodel.plugin.plugins import Plugins, PluginError
from lodel.settings.utils import SettingsError, SettingsErrors
from lodel.settings.validator import SettingValidator, LODEL2_CONF_SPECS
from lodel.settings.settings_loader import SettingsLoader
@ -37,6 +37,32 @@ PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
minor = sys.version_info.minor)
## @brief Handles configuration load etc.
#
# @par Basic usage
# For example if a file defines confs like :
# <pre>
# [super_section]
# super_conf = super_value
# </pre>
# You can access it with :
# <pre> settings_instance.confs.super_section.super_conf </pre>
#
# @par Init sequence
# The initialization sequence is a bit tricky. In fact, plugins adds allowed configuration
# sections/values, but the list of plugins to load in in... the settings.
# Here is the conceptual presentation of Settings class initialization stages :
# -# Preloading (sets values like lodel2 library path or the plugins path)
# -# Ask a @ref lodel.settings.setting_loader.SettingsLoader to load all configurations files
# -# Fetch the list of plugins in the loaded settings
# -# Merge plugins settings specification with the global lodel settings specs ( see @ref lodel.plugin )
# -# Fetch all settings from the merged settings specs
#
# @par Init sequence in practical
# In practice those steps are done by calling a succession of private methods :
# -# @ref Settings.__bootstrap() ( steps 1 to 3 )
# -# @ref Settings.__merge_specs() ( step 4 )
# -# @ref Settings.__populate_from_specs() (step 5)
# -# And finally @ref Settings.__confs_to_namedtuple()
#
# @todo handles default sections for variable sections (sections ending with '.*')
class Settings(object):
@ -52,10 +78,12 @@ class Settings(object):
self.__confs = dict()
self.__conf_dir = conf_dir
self.__load_bootstrap_conf(conf_file)
# now we should have the self.__confs['lodel2']['plugins_paths'] and
# self.__confs['lodel2']['lib_path'] set
# now we should have the self.__confs['lodel2']['plugins_paths']
# and self.__confs['lodel2']['lib_path'] set
self.__bootstrap()
## @brief Configuration keys accessor
# @return All confs organised into named tuples
@property
def confs(self):
return copy.copy(self.__confs)
@ -112,9 +140,12 @@ class Settings(object):
# Construct final specs dict replacing variable sections
# by the actual existing sections
variable_sections = [ section for section in specs if section.endswith('.*') ]
print("DEBUG VARIABLE SECTIONS : ")
for vsec in variable_sections:
preffix = vsec[:-2]
print("PREFFIX = ", preffix)
for section in loader.getsection(preffix, 'default'): #WARNING : hardcoded default section
print("SECTIONs = ", section)
specs[section] = copy.copy(specs[vsec])
del(specs[vsec])
# Fetching valuds for sections

View file

@ -0,0 +1,2 @@
__author__ = "Lodel2 dev team"
__fullname__ = "Dummy plugin"

17
plugins/dummy/main.py Normal file
View file

@ -0,0 +1,17 @@
#-*- coding: utf-8 -*-
from lodel.plugin import LodelHook
## @brief Hook's callback example
@LodelHook('leapi_get_pre')
@LodelHook('leapi_get_post')
@LodelHook('leapi_update_pre')
@LodelHook('leapi_update_post')
@LodelHook('leapi_delete_pre')
@LodelHook('leapi_delete_post')
@LodelHook('leapi_insert_pre')
@LodelHook('leapi_insert_post')
def dummy_callback(hook_name, caller, payload):
if Lodel.settings.Settings.debug:
print("\tHook %s\tcaller %s with %s" % (hook_name, caller, payload))
return payload

View file

View file

@ -0,0 +1,3 @@
[lodel2]
lib_path = /home/yannweb/dev/lodel2/lodel2-git
plugins_path = /home/yannweb/dev/lodel2/lodel2-git/plugins

View file

@ -0,0 +1,4 @@
[lodel2.logging.default]
level = DEBUG
context = True
filename = -

View file

@ -0,0 +1,3 @@
[lodel2]
lib_path = /home/yannweb/dev/lodel2/lodel2-git
plugins_path = /home/yannweb/dev/lodel2/lodel2-git/plugins

View file

@ -1,42 +1,13 @@
#-*- coding: utf-8 -*-
import unittest
from lodel.settings.settings_loader import SettingsLoader
from unittest import mock
class SettingsLoaderTestCase(unittest.TestCase):
from lodel.settings.settings import Settings
def test_merge_getsection(self):
"""Tests merge and getSection functions """
settings = SettingsLoader('/home/helene/lodel2/tests/settings/conf.d')
a = settings.getsection('A')
self.assertEqual(a,dict({"a":"a1","b":"b1,b2,b3","c":"toto","fhui":"njl"}))
b = settings.getsection('B')
self.assertEqual(b,dict({"ab":"art","bb":"bj,kl,mn","cb":"tatat"}))
c = settings.getsection('C')
self.assertEqual(c,dict({"ca":"a2","cb":"b4,b2,b3","cc":"titi"}))
d = settings.getsection('D')
for v in a:
assert ('A','{"a":"a1","b":"b1,b2,b3","c":"toto","fhui":"njl"}')
def maFonction(a):
return a
e=settings.getoption('A','a',maFonction)
self.assertEqual(e,'a1')
f=settings.getoption('B','bb',maFonction)
self.assertEqual(f,"bj,kl,mn")
g=settings.getremains()
self.assertIsNotNone(g)
e=settings.getoption('A','b',maFonction)
e=settings.getoption('A','c',maFonction)
e=settings.getoption('A','fhui',maFonction)
f=settings.getoption('B','ab',maFonction)
f=settings.getoption('B','cb',maFonction)
f=settings.getoption('C','cb',maFonction)
f=settings.getoption('C','ca',maFonction)
f=settings.getoption('C','cc',maFonction)
g=settings.getremains()
self.assertEqual(g,[])
class SettingsTestCase(unittest.TestCase):
def test_init(self):
settings = Settings('tests/settings/settings_tests.ini', 'tests/settings/settings_tests_conf.d')
print(settings.confs)
pass

View file

@ -0,0 +1,42 @@
#-*- coding: utf-8 -*-
import unittest
from lodel.settings.settings_loader import SettingsLoader
class SettingsLoaderTestCase(unittest.TestCase):
def test_merge_getsection(self):
"""Tests merge and getSection functions """
settings = SettingsLoader('/home/helene/lodel2/tests/settings/conf.d')
a = settings.getsection('A')
self.assertEqual(a,dict({"a":"a1","b":"b1,b2,b3","c":"toto","fhui":"njl"}))
b = settings.getsection('B')
self.assertEqual(b,dict({"ab":"art","bb":"bj,kl,mn","cb":"tatat"}))
c = settings.getsection('C')
self.assertEqual(c,dict({"ca":"a2","cb":"b4,b2,b3","cc":"titi"}))
d = settings.getsection('D')
for v in a:
assert ('A','{"a":"a1","b":"b1,b2,b3","c":"toto","fhui":"njl"}')
def maFonction(a):
return a
e=settings.getoption('A','a',maFonction)
self.assertEqual(e,'a1')
f=settings.getoption('B','bb',maFonction)
self.assertEqual(f,"bj,kl,mn")
g=settings.getremains()
self.assertIsNotNone(g)
e=settings.getoption('A','b',maFonction)
e=settings.getoption('A','c',maFonction)
e=settings.getoption('A','fhui',maFonction)
f=settings.getoption('B','ab',maFonction)
f=settings.getoption('B','cb',maFonction)
f=settings.getoption('C','cb',maFonction)
f=settings.getoption('C','ca',maFonction)
f=settings.getoption('C','cc',maFonction)
g=settings.getremains()
self.assertEqual(g,[])