No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

plugins.py 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. #-*- coding: utf-8 -*-
  2. import inspect
  3. import os, os.path
  4. import warnings
  5. from Lodel.settings import Settings
  6. from Lodel import logger
  7. from Lodel.hooks import LodelHook
  8. from Lodel.user import authentication_method, identification_method
  9. ## @brief Handle registration and fetch of plugins functions that will be bound to LeCrud subclass
  10. #
  11. # @note This class exists because of strange behavior with decorator's class inheritance (static attributes was modified by child class)
  12. class LeapiPluginsMethods(object):
  13. ## @brief Stores instance methods we want to bind to LeCrud subclass instances
  14. __methods = {}
  15. ## @brief Stores classmethods we want to bind to LeCrud subclass
  16. __classmethods = {}
  17. ## @brief Register a new method for LeApi enhancement
  18. # @param class_method bool : if True register a classmethod
  19. # @param leapi_classname str : the classname
  20. # @param method callable : the method to bind
  21. # @param bind_name str|None : binding name, if None use the decorated function name
  22. @classmethod
  23. def register(cls, class_method, leapi_classname, method, bind_name = None):
  24. if bind_name is None:
  25. bind_name = method.__name__ # use method name if no bind_name specified
  26. methods = cls.__classmethods if class_method else cls.__methods
  27. if leapi_classname not in methods:
  28. methods[leapi_classname] = dict()
  29. elif bind_name in methods[leapi_classname]:
  30. orig_fun = methods[leapi_classname][bind_name]
  31. msg = 'Method overwriting : %s.%s will be registered as %s for %s in place of %s.%s' % (
  32. inspect.getmodule(orig_fun).__name__,
  33. orig_fun.__name__,
  34. bind_name,
  35. leapi_classname,
  36. inspect.getmodule(method).__name__,
  37. method.__name__
  38. )
  39. logger.warning(msg)
  40. methods[leapi_classname][bind_name] = method
  41. # I was thinking that dict are refrerences...
  42. if class_method:
  43. cls.__classmethods = methods
  44. else:
  45. cls.__methods = methods
  46. ## @brief Fetch all methods that has to be bound to a class
  47. # @param class_method bool : if true returns classmethods
  48. # @param call_cls LeCrud subclass : the targeted class
  49. # @return a dict with wanted bind name as key and callable as value
  50. @classmethod
  51. def get_methods(cls, class_method, call_cls):
  52. methods = cls.__classmethods if class_method else cls.__methods
  53. result = dict()
  54. for leapi_classname in methods:
  55. leapi_cls = call_cls.name2class(leapi_classname)
  56. # Strange tests are made on call_cls.__name__ because the name2class method
  57. # is not working when called from LeObject at init time...
  58. if leapi_cls is False and leapi_classname != call_cls.__name__:
  59. logger.warning("Unable to find leapi class %s" % (leapi_classname))
  60. elif leapi_classname == call_cls.__name__ or issubclass(call_cls, leapi_cls):
  61. result.update(methods[leapi_classname])
  62. return result
  63. ## @brief Decorator class for leapi object enhancement
  64. #
  65. # This decorator allows plugins to bind methods to leapi
  66. # classes.
  67. #
  68. # The decorator take 2 arguments :
  69. # - leapi_cls_name str : the leapi class name
  70. # - method_name str (optional) : the method name once bind (if None the decorated function name will be used)
  71. #
  72. # @note there is another optionnal argument class_method (a bool), but you should not use it and use the leapi_classmethod decorator instead
  73. class leapi_method(object):
  74. ## @brief Constructor for plugins leapi enhancement methods
  75. # @param leapi_cls_name str : the classname we want to bind a method to
  76. # @param method_name str|None : binding name, if None use the decorated function name
  77. def __init__(self, leapi_cls_name, method_name = None, class_method = False):
  78. self.__leapi_cls_name = leapi_cls_name
  79. self.__method_name = method_name
  80. self.__method = None
  81. self.__class_method = bool(class_method)
  82. ## @brief Called at decoration time
  83. # @param method callable : the decorated function
  84. def __call__(self, method):
  85. self.__method = method
  86. if self.__method_name is None:
  87. self.__method_name = method.__name__
  88. self.__register()
  89. ## @biref Register a method to the method we want to bind
  90. def __register(self):
  91. LeapiPluginsMethods.register(self.__class_method, self.__leapi_cls_name, self.__method, self.__method_name)
  92. ## @brief Same behavior thant leapi_method but registers classmethods
  93. class leapi_classmethod(leapi_method):
  94. def __init__(self, leapi_cls_name, method_name = None):
  95. super().__init__(leapi_cls_name, method_name, True)
  96. ## @brief Returns a list of human readable registered hooks
  97. # @param names list | None : optionnal filter on name
  98. # @param plugins list | None : optionnal filter on plugin name
  99. # @return A str representing registered hooks
  100. def list_hooks(names = None, plugins = None):
  101. res = ""
  102. # Hooks registered and handled by Lodel.usera
  103. for decorator in [authentication_method, identification_method]:
  104. if names is None or decorator.__name__ in names:
  105. res += "%s :\n" % decorator.__name__
  106. for hook in decorator.list_methods():
  107. module = inspect.getmodule(hook).__name__
  108. if plugins is not None: # Filter by plugin
  109. spl = module.split('.')
  110. if spl[-1] not in plugins:
  111. continue
  112. res += "\t- %s.%s\n" % (module, hook.__name__)
  113. # Hooks registered and handled by Lodel.hooks
  114. registered_hooks = LodelHook.hook_list(names)
  115. for hook_name, hooks in registered_hooks.items():
  116. if names is None or hook_name in names:
  117. res += "%s :\n" % hook_name
  118. for hook, priority in hooks:
  119. module = inspect.getmodule(hook).__name__
  120. if plugins is not None: # Filter by plugin
  121. spl = module.split('.')
  122. if spl[-1] not in plugins:
  123. continue
  124. res += "\t- %s.%s ( priority %d )\n" % (module, hook.__name__, priority)
  125. return res
  126. ## @brief Return a human readable list of plugins
  127. # @param activated bool | None : Optionnal filter on activated or not plugin
  128. # @return a str
  129. def list_plugins(activated = None):
  130. res = ""
  131. # Activated plugins
  132. if activated is None or activated:
  133. res += "Activated plugins :\n"
  134. for plugin_name in Settings.plugins:
  135. res += "\t- %s\n" % plugin_name
  136. # Deactivated plugins
  137. if activated is None or not activated:
  138. plugin_dir = os.path.join(Settings.lodel2_lib_path, 'plugins')
  139. res += "Not activated plugins :\n"
  140. all_plugins = [fname for fname in os.listdir(plugin_dir) if fname != '.' and fname != '..' and fname != '__init__.py']
  141. for plugin_name in all_plugins:
  142. if os.path.isfile(os.path.join(plugin_dir, plugin_name)) and plugin_name.endswith('.py'):
  143. plugin_name = ''.join(plugin_name.split('.')[:-1])
  144. elif not os.path.isdir(os.path.join(plugin_dir, plugin_name)):
  145. warnings.warn("Dropped file in plugins directory : '%s'" % (os.path.join(plugin_dir, plugin_name)))
  146. continue
  147. elif plugin_name == '__pycache__':
  148. continue
  149. if plugin_name not in Settings.plugins:
  150. res += "\t- %s\n" % plugin_name
  151. return res
  152. ## @brief Utility function that generate the __all__ list of the plugins/__init__.py file
  153. # @return A list of module name to import
  154. def _all_plugins():
  155. plugin_dir = os.path.join(Settings.lodel2_lib_path, 'plugins')
  156. res = list()
  157. for plugin_name in Settings.plugins:
  158. if os.path.isdir(os.path.join(plugin_dir, plugin_name)):
  159. # plugin is a module
  160. res.append('%s' % plugin_name)
  161. #res.append('%s.loader' % plugin_name)
  162. elif os.path.isfile(os.path.join(plugin_dir, '%s.py' % plugin_name)):
  163. # plugin is a simple python sourcefile
  164. res.append('%s' % plugin_name)
  165. return res