Browse Source

Hooks & plugins first implementation

Yann Weber 9 years ago
parent
commit
cb9b53e7a2
7 changed files with 128 additions and 1 deletions
  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 View File

@@ -0,0 +1,68 @@
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 View File

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

+ 2
- 0
install/loader.py View File

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

+ 33
- 0
leapi/lecrud.py View File

@@ -10,6 +10,7 @@ import importlib
10 10
 import re
11 11
 
12 12
 from EditorialModel.fieldtypes.generic import DatasConstructor
13
+from Lodel.hooks import LodelHook
13 14
 
14 15
 REL_SUP = 0
15 16
 REL_SUB = 1
@@ -279,6 +280,15 @@ class _LeCrud(object):
279 280
     # @todo better error handling
280 281
     # @todo for check_data_consistency, datas must be populated to make update safe !
281 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 292
         if not self.is_complete():
283 293
             self.populate()
284 294
             warnings.warn("\nThis object %s is not complete and has been populated when update was called. This is very unsafe\n" % self)
@@ -297,7 +307,9 @@ class _LeCrud(object):
297 307
     # @return True if success
298 308
     # @todo better error handling
299 309
     def delete(self):
310
+        LodelHook.call_hook('leapi_delete_pre', self, None)
300 311
         self._datasource.delete(self.__class__, self.uidget())
312
+        return LodelHook.call_hook('leapi_delete_post', self, None)
301 313
 
302 314
     ## @brief Check that datas are valid for this type
303 315
     # @param datas dict : key == field name value are field values
@@ -358,6 +370,17 @@ class _LeCrud(object):
358 370
     # @todo think about LeObject and LeClass instanciation (partial instanciation, etc)
359 371
     @classmethod
360 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 384
         if field_list is None or len(field_list) == 0:
362 385
             #default field_list
363 386
             field_list = cls.fieldlist()
@@ -409,6 +432,16 @@ class _LeCrud(object):
409 432
     # @return A new id if success else False
410 433
     @classmethod
411 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 445
         callcls = cls if classname is None else cls.name2class(classname)
413 446
         if not callcls:
414 447
             raise LeApiErrors("Error when inserting",{'error':ValueError("The class '%s' was not found"%classname)})

+ 2
- 0
plugins/__init__.py View File

@@ -0,0 +1,2 @@
1
+import Lodel.settings
2
+__all__ = Lodel.settings.Settings.plugins

+ 18
- 0
plugins/dummy.py View File

@@ -0,0 +1,18 @@
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 View File

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

Loading…
Cancel
Save