|
@@ -8,8 +8,9 @@ import json
|
8
|
8
|
from importlib.machinery import SourceFileLoader, SourcelessFileLoader
|
9
|
9
|
|
10
|
10
|
import plugins
|
11
|
|
-from .exceptions import *
|
12
|
11
|
from lodel import logger
|
|
12
|
+from lodel.settings.utils import SettingsError
|
|
13
|
+from .exceptions import *
|
13
|
14
|
|
14
|
15
|
## @package lodel.plugins Lodel2 plugins management
|
15
|
16
|
#
|
|
@@ -26,7 +27,7 @@ VIRTUAL_TEMP_PACKAGE_NAME = 'lodel.plugin_tmp'
|
26
|
27
|
##@brief Plugin init filename
|
27
|
28
|
INIT_FILENAME = '__init__.py' # Loaded with settings
|
28
|
29
|
PLUGIN_NAME_VARNAME = '__plugin_name__'
|
29
|
|
-PLUGIN_TYPE_VARNAME = '__type__'
|
|
30
|
+PLUGIN_TYPE_VARNAME = '__plugin_type__'
|
30
|
31
|
PLUGIN_VERSION_VARNAME = '__version__'
|
31
|
32
|
CONFSPEC_FILENAME_VARNAME = '__confspec__'
|
32
|
33
|
CONFSPEC_VARNAME = 'CONFSPEC'
|
|
@@ -41,8 +42,8 @@ DEFAULT_PLUGINS_PATH_LIST = ['./plugins']
|
41
|
42
|
MANDATORY_VARNAMES = [PLUGIN_NAME_VARNAME, LOADER_FILENAME_VARNAME,
|
42
|
43
|
PLUGIN_VERSION_VARNAME]
|
43
|
44
|
|
44
|
|
-EXTENSIONS = 'default'
|
45
|
|
-PLUGINS_TYPES = [EXTENSIONS, 'datasource', 'session_handler', 'ui']
|
|
45
|
+DEFAULT_PLUGIN_TYPE = 'extension'
|
|
46
|
+PLUGINS_TYPES = [DEFAULT_PLUGIN_TYPE, 'datasource', 'session_handler', 'ui']
|
46
|
47
|
|
47
|
48
|
|
48
|
49
|
##@brief Describe and handle version numbers
|
|
@@ -147,6 +148,35 @@ to generic PluginVersion comparison function : '%s'" % cmp_fun_name)
|
147
|
148
|
return {'major': self.major, 'minor': self.minor,
|
148
|
149
|
'revision': self.revision}
|
149
|
150
|
|
|
151
|
+##@brief Stores plugin class registered
|
|
152
|
+__all_ptypes = list()
|
|
153
|
+
|
|
154
|
+##@brief Plugin metaclass that allows to "catch" child class
|
|
155
|
+#declaration
|
|
156
|
+#
|
|
157
|
+#Automatic script registration on child class declaration
|
|
158
|
+class MetaPlugType(type):
|
|
159
|
+
|
|
160
|
+ def __init__(self, name, bases, attrs):
|
|
161
|
+ #Here we can store all child classes of Plugin
|
|
162
|
+ super().__init__(name, bases, attrs)
|
|
163
|
+ if len(bases) == 1 and bases[0] == object:
|
|
164
|
+ print("Dropped : ", name, bases)
|
|
165
|
+ return
|
|
166
|
+ self.__register_types()
|
|
167
|
+ #list_name= [cls.__name__ for cls in __all_ptypes]
|
|
168
|
+ #if self.name in list_name:
|
|
169
|
+ # return
|
|
170
|
+ #else:
|
|
171
|
+ # plug_type_register(self)
|
|
172
|
+
|
|
173
|
+ def __register_types(self):
|
|
174
|
+ plug_type_register(self)
|
|
175
|
+
|
|
176
|
+def plug_type_register(cls):
|
|
177
|
+ __all_ptypes.append(cls)
|
|
178
|
+ logger.info("New child class registered : %s" % cls.__name__)
|
|
179
|
+
|
150
|
180
|
|
151
|
181
|
##@brief Handle plugins
|
152
|
182
|
#
|
|
@@ -157,7 +187,7 @@ to generic PluginVersion comparison function : '%s'" % cmp_fun_name)
|
157
|
187
|
# 1. Settings call start method to instanciate all plugins found in confs
|
158
|
188
|
# 2. Settings fetch all confspecs
|
159
|
189
|
# 3. the loader call load_all to register hooks etc
|
160
|
|
-class Plugin(object):
|
|
190
|
+class Plugin(object, metaclass=MetaPlugType):
|
161
|
191
|
|
162
|
192
|
##@brief Stores plugin directories paths
|
163
|
193
|
_plugin_directories = None
|
|
@@ -171,6 +201,9 @@ class Plugin(object):
|
171
|
201
|
|
172
|
202
|
##@brief Attribute that stores plugins list from discover cache file
|
173
|
203
|
_plugin_list = None
|
|
204
|
+
|
|
205
|
+ ##@brief Store dict representation of discover cache content
|
|
206
|
+ _discover_cache = None
|
174
|
207
|
|
175
|
208
|
##@brief Plugin class constructor
|
176
|
209
|
#
|
|
@@ -248,7 +281,7 @@ init file. Malformed plugin"
|
248
|
281
|
try:
|
249
|
282
|
self.__type = getattr(self.module, PLUGIN_TYPE_VARNAME)
|
250
|
283
|
except AttributeError:
|
251
|
|
- self.__type = EXTENSIONS
|
|
284
|
+ self.__type = DEFAULT_PLUGIN_TYPE
|
252
|
285
|
self.__type = str(self.__type).lower()
|
253
|
286
|
if self.__type not in PLUGINS_TYPES:
|
254
|
287
|
raise PluginError("Unknown plugin type '%s'" % self.__type)
|
|
@@ -426,6 +459,50 @@ name differ from the one found in plugin's init file"
|
426
|
459
|
def confspecs(self):
|
427
|
460
|
return copy.copy(self.__confspecs)
|
428
|
461
|
|
|
462
|
+ ##@brief Retrieves plugin list confspecs
|
|
463
|
+ #
|
|
464
|
+ #This method ask for each Plugin child class the confspecs specifying where
|
|
465
|
+ #the wanted plugin list is stored. (For example DatasourcePlugin expect
|
|
466
|
+ #that a list of ds plugin to load stored in lodel2 section, datasources key
|
|
467
|
+ # etc...
|
|
468
|
+ @classmethod
|
|
469
|
+ def plugin_list_confspec(cls):
|
|
470
|
+ from lodel.settings.validator import confspec_append
|
|
471
|
+ res = dict()
|
|
472
|
+ for pcls in cls.plugin_types():
|
|
473
|
+ plcs = pcls.plist_confspec()
|
|
474
|
+ confspec_append(res, plcs)
|
|
475
|
+ return res
|
|
476
|
+
|
|
477
|
+ ##@brief Attempt to read plugin discover cache
|
|
478
|
+ #@note If no cache yet make a discover with default plugin directory
|
|
479
|
+ #@return a dict (see @ref _discover() )
|
|
480
|
+ @classmethod
|
|
481
|
+ def plugin_cache(cls):
|
|
482
|
+ if cls._discover_cache is None:
|
|
483
|
+ if not os.path.isfile(DISCOVER_CACHE_FILENAME):
|
|
484
|
+ cls.discover()
|
|
485
|
+ with open(DISCOVER_CACHE_FILENAME) as pdcache_fd:
|
|
486
|
+ res = json.load(pdcache_fd)
|
|
487
|
+ #Check consistency of loaded cache
|
|
488
|
+ if 'path_list' not in res:
|
|
489
|
+ raise LodelFatalError("Malformed plugin's discover cache file \
|
|
490
|
+: '%s'. Unable to find plugin's paths list." % DISCOVER_CACHE_FILENAME)
|
|
491
|
+ expected_keys = ['type', 'path', 'version']
|
|
492
|
+ for pname in res['plugins']:
|
|
493
|
+ for ekey in expected_keys:
|
|
494
|
+ if ekey not in res['plugins'][pname]:
|
|
495
|
+ #Bad cache !
|
|
496
|
+ logger.warning("Malformed plugin's discover cache \
|
|
497
|
+file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
|
|
498
|
+ cls._discover_cache = cls.discover(res['path_list'])
|
|
499
|
+ break
|
|
500
|
+ else:
|
|
501
|
+ #The cache we just read was OK
|
|
502
|
+ cls._discover_cache = res
|
|
503
|
+
|
|
504
|
+ return cls._discover_cache
|
|
505
|
+
|
429
|
506
|
##@brief Register a new plugin
|
430
|
507
|
#
|
431
|
508
|
#@param plugin_name str : The plugin name
|
|
@@ -433,11 +510,23 @@ name differ from the one found in plugin's init file"
|
433
|
510
|
#@throw PluginError
|
434
|
511
|
@classmethod
|
435
|
512
|
def register(cls, plugin_name):
|
|
513
|
+ from .datasource_plugin import DatasourcePlugin
|
436
|
514
|
if plugin_name in cls._plugin_instances:
|
437
|
515
|
msg = "Plugin allready registered with same name %s"
|
438
|
516
|
msg %= plugin_name
|
439
|
517
|
raise PluginError(msg)
|
440
|
|
- plugin = cls(plugin_name)
|
|
518
|
+ #Here we check that previous discover found a plugin with that name
|
|
519
|
+ pdcache = cls.plugin_cache()
|
|
520
|
+ if plugin_name not in pdcache['plugins']:
|
|
521
|
+ raise PluginError("No plugin named %s found" % plugin_name)
|
|
522
|
+ pinfos = pdcache['plugins'][plugin_name]
|
|
523
|
+ ptype = pinfos['type']
|
|
524
|
+ if ptype == 'datasource':
|
|
525
|
+ pcls = DatasourcePlugin
|
|
526
|
+ else:
|
|
527
|
+ pcls = cls
|
|
528
|
+ print(plugin_name, ptype, pcls)
|
|
529
|
+ plugin = pcls(plugin_name)
|
441
|
530
|
cls._plugin_instances[plugin_name] = plugin
|
442
|
531
|
logger.debug("Plugin %s available." % plugin)
|
443
|
532
|
return plugin
|
|
@@ -530,8 +619,7 @@ name differ from the one found in plugin's init file"
|
530
|
619
|
result = dict()
|
531
|
620
|
for pinfos in tmp_res:
|
532
|
621
|
pname = pinfos['name']
|
533
|
|
- if (
|
534
|
|
- pname in result
|
|
622
|
+ if ( pname in result
|
535
|
623
|
and pinfos['version'] > result[pname]['version'])\
|
536
|
624
|
or pname not in result:
|
537
|
625
|
result[pname] = pinfos
|
|
@@ -566,6 +654,11 @@ name differ from the one found in plugin's init file"
|
566
|
654
|
cls._plugin_list = infos['plugins']
|
567
|
655
|
return cls._plugin_list
|
568
|
656
|
|
|
657
|
+ ##@brief Return a list of child Class Plugin
|
|
658
|
+ @classmethod
|
|
659
|
+ def plugin_types(cls):
|
|
660
|
+ return cls.__all_ptypes
|
|
661
|
+
|
569
|
662
|
##@brief Attempt to open and load plugin discover cache
|
570
|
663
|
#@return discover cache
|
571
|
664
|
#@throw PluginError when open or load fails
|
|
@@ -617,16 +710,23 @@ name differ from the one found in plugin's init file"
|
617
|
710
|
log_msg %= (attr_name, INIT_FILENAME)
|
618
|
711
|
logger.debug(log_msg)
|
619
|
712
|
return False
|
|
713
|
+ #Fetching plugin's version
|
620
|
714
|
try:
|
621
|
715
|
pversion = getattr(initmod, PLUGIN_VERSION_VARNAME)
|
622
|
|
- except PluginError as e:
|
|
716
|
+ except (NameError, AttributeError) as e:
|
623
|
717
|
msg = "Invalid plugin version found in %s : %s"
|
624
|
718
|
msg %= (path, e)
|
625
|
719
|
raise PluginError(msg)
|
|
720
|
+ #Fetching plugin's type
|
|
721
|
+ try:
|
|
722
|
+ ptype = getattr(initmod, PLUGIN_TYPE_VARNAME)
|
|
723
|
+ except (NameError, AttributeError) as e:
|
|
724
|
+ ptype = DEFAULT_PLUGIN_TYPE
|
626
|
725
|
pname = getattr(initmod, PLUGIN_NAME_VARNAME)
|
627
|
726
|
return {'name': pname,
|
628
|
727
|
'version': pversion,
|
629
|
|
- 'path': path}
|
|
728
|
+ 'path': path,
|
|
729
|
+ 'type': ptype}
|
630
|
730
|
|
631
|
731
|
##@brief Import init file from a plugin path
|
632
|
732
|
#@param path str : Directory path
|
|
@@ -795,14 +895,13 @@ with %s" % (custom_method._method_name, custom_method))
|
795
|
895
|
|
796
|
896
|
class SessionHandler(Plugin):
|
797
|
897
|
__instance = None
|
798
|
|
-
|
799
|
|
- def __new__(cls):
|
800
|
|
- if cls.__instance == None:
|
801
|
|
- cls.instance == object.__new__(cls)
|
802
|
|
- return cls.__instance
|
803
|
898
|
|
804
|
899
|
def __init__(self, plugin_name):
|
805
|
|
- super(Plugin, self).__init__(plugin_name)
|
|
900
|
+ if self.__instance is None:
|
|
901
|
+ super(Plugin, self).__init__(plugin_name)
|
|
902
|
+ self.__instance = True
|
|
903
|
+ else:
|
|
904
|
+ raise RuntimeError("A SessionHandler Plugin is already plug")
|
806
|
905
|
|
807
|
906
|
class InterfacePlugin(Plugin):
|
808
|
907
|
def __init__(self, plugin_name):
|