Bläddra i källkod

Hooks & plugins first implementation

Yann Weber 9 år sedan
förälder
incheckning
cb9b53e7a2
7 ändrade filer med 128 tillägg och 1 borttagningar
  1. 68
    0
      Lodel/hooks.py
  2. 2
    1
      Lodel/settings_format.py
  3. 2
    0
      install/loader.py
  4. 33
    0
      leapi/lecrud.py
  5. 2
    0
      plugins/__init__.py
  6. 18
    0
      plugins/dummy.py
  7. 3
    0
      settings.py

+ 68
- 0
Lodel/hooks.py Visa fil

1
+#-*- coding: utf-8 -*-
2
+
3
+import os
4
+from importlib.machinery import SourceFileLoader
5
+
6
+from Lodel.settings import Settings
7
+
8
+## @brief Class designed to handle a hook's callback with a priority
9
+class DecoratedWrapper(object):
10
+    ## @brief Constructor
11
+    # @param hook function : the function to wrap
12
+    # @param priority int : the callbacl priority
13
+    def __init__(self, hook, priority):
14
+        self._priority = priority
15
+        self._hook = hook
16
+    
17
+    ## @brief Call the callback
18
+    # @param *args
19
+    # @param **kwargs
20
+    # @return modified payload
21
+    def __call__(self, hook_name, caller, payload):
22
+        return self._hook(hook_name, caller, payload)
23
+
24
+## @brief Decorator designed to register hook's callbacks
25
+#
26
+# Example : 
27
+#
28
+# <pre>
29
+# @LodelHook('hook_name', 42)
30
+# def super_callback(hook_name, caller, payload):
31
+#    return payload
32
+#
33
+# LodelHook.call_hook('hook_name', caller, 'foobar') #calls super_callback('hook_name', caller, 'foobar')
34
+# </pre>
35
+class LodelHook(object):
36
+    
37
+    ## @brief Stores all hooks (DecoratedWrapper instances)
38
+    _hooks = dict()
39
+    
40
+    ## @brief Decorator constructor
41
+    # @param hook_name str : the name of the hook to register to
42
+    # @param priority int : the hook priority
43
+    def __init__(self, hook_name, priority = None):
44
+        self._hook_name = hook_name
45
+        self._priority = 0xFFF if priority is None else priority
46
+    
47
+    ## @brief called just after __init__
48
+    # @param hook function : the decorated function
49
+    # @return the hook argument
50
+    def __call__(self, hook):
51
+        if not self._hook_name in self._hooks:
52
+            self._hooks[self._hook_name] = list()
53
+        wrapped = DecoratedWrapper(hook, self._priority)
54
+        self._hooks[self._hook_name].append(wrapped)
55
+        self._hooks[self._hook_name] = sorted(self._hooks[self._hook_name], key = lambda h: h._priority)
56
+        return hook
57
+
58
+    ## @brief Call hooks
59
+    # @param hook_name str : the hook's name
60
+    # @param payload * : datas for the hook
61
+    # @return modified payload
62
+    @classmethod
63
+    def call_hook(cls, hook_name, caller, payload):
64
+        if hook_name in cls._hooks:
65
+            for hook in cls._hooks[hook_name]:
66
+                payload = hook(hook_name, caller, payload)
67
+        return payload
68
+

+ 2
- 1
Lodel/settings_format.py Visa fil

13
     'datasource',
13
     'datasource',
14
     'mh_classname',
14
     'mh_classname',
15
     'migration_options',
15
     'migration_options',
16
-    'base_path'
16
+    'base_path',
17
+    'plugins',
17
 ]
18
 ]
18
 
19
 
19
 ## @brief List allowed (but not mandatory) configurations keys
20
 ## @brief List allowed (but not mandatory) configurations keys

+ 2
- 0
install/loader.py Visa fil

11
 Settings.load_module(instance_settings)
11
 Settings.load_module(instance_settings)
12
 globals()['Settings'] = Settings
12
 globals()['Settings'] = Settings
13
 
13
 
14
+from plugins import * #Load activated plugins
15
+
14
 # Import dynamic code
16
 # Import dynamic code
15
 if os.path.isfile(Settings.dynamic_code_file):
17
 if os.path.isfile(Settings.dynamic_code_file):
16
     from dynleapi import *
18
     from dynleapi import *

+ 33
- 0
leapi/lecrud.py Visa fil

10
 import re
10
 import re
11
 
11
 
12
 from EditorialModel.fieldtypes.generic import DatasConstructor
12
 from EditorialModel.fieldtypes.generic import DatasConstructor
13
+from Lodel.hooks import LodelHook
13
 
14
 
14
 REL_SUP = 0
15
 REL_SUP = 0
15
 REL_SUB = 1
16
 REL_SUB = 1
279
     # @todo better error handling
280
     # @todo better error handling
280
     # @todo for check_data_consistency, datas must be populated to make update safe !
281
     # @todo for check_data_consistency, datas must be populated to make update safe !
281
     def update(self, datas=None):
282
     def update(self, datas=None):
283
+        kwargs = locals()
284
+        del(kwargs['self'])
285
+        kwargs = LodelHook.call_hook('leapi_update_pre', self, kwargs)
286
+        ret = self.__update_unsafe(**kwargs)
287
+        return ret
288
+
289
+    ## @brief Unsafe, without hooks version of insert method
290
+    # @see _LeCrud.update()
291
+    def __update_unsafe(self, datas=None):
282
         if not self.is_complete():
292
         if not self.is_complete():
283
             self.populate()
293
             self.populate()
284
             warnings.warn("\nThis object %s is not complete and has been populated when update was called. This is very unsafe\n" % self)
294
             warnings.warn("\nThis object %s is not complete and has been populated when update was called. This is very unsafe\n" % self)
297
     # @return True if success
307
     # @return True if success
298
     # @todo better error handling
308
     # @todo better error handling
299
     def delete(self):
309
     def delete(self):
310
+        LodelHook.call_hook('leapi_delete_pre', self, None)
300
         self._datasource.delete(self.__class__, self.uidget())
311
         self._datasource.delete(self.__class__, self.uidget())
312
+        return LodelHook.call_hook('leapi_delete_post', self, None)
301
 
313
 
302
     ## @brief Check that datas are valid for this type
314
     ## @brief Check that datas are valid for this type
303
     # @param datas dict : key == field name value are field values
315
     # @param datas dict : key == field name value are field values
358
     # @todo think about LeObject and LeClass instanciation (partial instanciation, etc)
370
     # @todo think about LeObject and LeClass instanciation (partial instanciation, etc)
359
     @classmethod
371
     @classmethod
360
     def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
372
     def get(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
373
+        kwargs = locals()
374
+        del(kwargs['cls'])
375
+        kwargs = LodelHook.call_hook('leapi_get_pre', cls, kwargs)
376
+        ret = cls.__get_unsafe(**kwargs)
377
+        return LodelHook.call_hook('leapi_get_post', cls, ret)
378
+
379
+    ## @brief Unsafe, without hooks version of get() method
380
+    # @see _LeCrud.get()
381
+    @classmethod
382
+    def __get_unsafe(cls, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
383
+
361
         if field_list is None or len(field_list) == 0:
384
         if field_list is None or len(field_list) == 0:
362
             #default field_list
385
             #default field_list
363
             field_list = cls.fieldlist()
386
             field_list = cls.fieldlist()
409
     # @return A new id if success else False
432
     # @return A new id if success else False
410
     @classmethod
433
     @classmethod
411
     def insert(cls, datas, classname=None):
434
     def insert(cls, datas, classname=None):
435
+        kwargs = locals()
436
+        del(kwargs['cls'])
437
+        kwargs = LodelHook.call_hook('leapi_insert_pre', cls, kwargs)
438
+        ret = cls.__insert_unsafe(**kwargs)
439
+        return LodelHook.call_hook('leapi_insert_post', cls, ret)
440
+
441
+    ## @brief Unsafe, without hooks version of insert() method
442
+    # @see _LeCrud.insert()
443
+    @classmethod
444
+    def __insert_unsafe(cls, datas, classname=None):
412
         callcls = cls if classname is None else cls.name2class(classname)
445
         callcls = cls if classname is None else cls.name2class(classname)
413
         if not callcls:
446
         if not callcls:
414
             raise LeApiErrors("Error when inserting",{'error':ValueError("The class '%s' was not found"%classname)})
447
             raise LeApiErrors("Error when inserting",{'error':ValueError("The class '%s' was not found"%classname)})

+ 2
- 0
plugins/__init__.py Visa fil

1
+import Lodel.settings
2
+__all__ = Lodel.settings.Settings.plugins

+ 18
- 0
plugins/dummy.py Visa fil

1
+#-*- coding: utf-8 -*-
2
+
3
+import Lodel.settings
4
+from Lodel.hooks import LodelHook
5
+
6
+## @brief Hook's callback example
7
+@LodelHook('leapi_get_pre')
8
+@LodelHook('leapi_get_post')
9
+@LodelHook('leapi_update_pre')
10
+@LodelHook('leapi_update_post')
11
+@LodelHook('leapi_delete_pre')
12
+@LodelHook('leapi_delete_post')
13
+@LodelHook('leapi_insert_pre')
14
+@LodelHook('leapi_insert_post')
15
+def dummy_callback(hook_name, caller, payload):
16
+    if Lodel.settings.Settings.debug:
17
+        print("\tHook %s\tcaller %s with %s" % (hook_name, caller, payload))
18
+    return payload   

+ 3
- 0
settings.py Visa fil

3
 
3
 
4
 import pymysql
4
 import pymysql
5
 import os
5
 import os
6
+import os.path
6
 
7
 
7
 lodel2_lib_path = os.path.dirname(os.path.abspath(__file__))
8
 lodel2_lib_path = os.path.dirname(os.path.abspath(__file__))
8
 base_path = os.path.dirname(os.path.abspath(__file__))
9
 base_path = os.path.dirname(os.path.abspath(__file__))
9
 debug = False
10
 debug = False
10
 debug_sql = False
11
 debug_sql = False
11
 
12
 
13
+plugins = ['dummy']
14
+
12
 datasource = {
15
 datasource = {
13
     'default': {
16
     'default': {
14
         'module':pymysql,
17
         'module':pymysql,

Loading…
Avbryt
Spara