Parcourir la source

Merge branch 'newlodel' of git.labocleo.org:lodel2 into newlodel

prieto il y a 9 ans
Parent
révision
40d2df4f05

+ 83
- 39
lodel/leapi/query.py Voir le fichier

@@ -7,92 +7,121 @@ from .leobject import LeObject, LeApiErrors, LeApiDataCheckError
7 7
 class LeQueryError(Exception):
8 8
     pass
9 9
 
10
-
11 10
 class LeQuery(object):
12 11
 
13 12
     ## @brief The datasource object used for this query
14
-    _datasource = None
13
+    datasource = None
15 14
 
16 15
     ## @brief The available operators used in query definitions
17
-    _query_operators = ['=', '<=', '>=', '!=', '<', '>', ' in ', ' not in ', ' like ', ' not like ']
16
+    query_operators = ['=', '<=', '>=', '!=', '<', '>', ' in ', ' not in ', ' like ', ' not like ']
18 17
 
18
+    ## @brief Constructor
19
+    # @param target_class EmClass : class of the object to query about
19 20
     def __init__(self, target_class):
20 21
         if not issubclass(target_class, LeObject):
21 22
             raise TypeError("target class has to be a child class of LeObject")
22
-        self._target_class = target_class
23
+        self.target_class = target_class
23 24
 
24 25
 
26
+## @brief Class representing an Insert query
25 27
 class LeInsertQuery(LeQuery):
26
-    action = 'insert'
27
-
28
-    def __init__(self, target_class, datas, classname=None):
29
-        targeted_class = target_class if classname is None else LeObject.name2class(classname)
30
-        if not targeted_class:
31
-            raise LeQueryError('Error when inserting', {'error': ValueError("The class '%s' was not found" % classname)})
32 28
 
33
-        super().__init__(targeted_class)
29
+    ## @brief Constructor
30
+    # @param target_class EmClass: class corresponding to the inserted object
31
+    # @param datas dict : datas to insert
32
+    def __init__(self, target_class, datas):
33
+        super().__init__(target_class)
34 34
         self.datas = datas
35 35
 
36
-    # @todo Reactivate the LodelHooks call when this class is implemented
36
+    ## @brief executes the insert query
37
+    # @return bool
38
+    # @TODO reactivate the LodelHooks call when this class is implemented
37 39
     def execute(self):
38
-        datas = self.datas  # TODO : replace with LodelHooks.call_hook('leapi_insert_pre', self._target_class, self.datas)
40
+        datas = self.datas  # LodelHooks.call_hook('leapi_insert_pre', self.target_class, self.datas)
39 41
         ret = self.__insert(**datas)
40
-        # ret = LodelHook.call_hook('leapi_insert_post', self._target_class, ret)
42
+        # ret = LodelHook.call_hook('leapi_insert_post', self.target_class, ret)
41 43
         return ret
42 44
 
43
-    def __insert(self):
44
-        insert_datas = self._target_class.prepare_datas(self.datas, complete=True, allow_internal=True)
45
-        return self._datasource.insert(self._target_class, **insert_datas)
45
+    ## @brief calls the datasource to perform the insert command
46
+    # @param datas dict : formatted datas corresponding to the insert
47
+    # @return str : the uid of the inserted object
48
+    def __insert(self, **datas):
49
+        insert_datas = self.target_class.prepare_datas(datas, complete=True, allow_internal=True)
50
+        res = self.datasource.insert(self.target_class, **insert_datas)
51
+        return res
46 52
 
47 53
 
54
+## @brief Class representing an Abstract Filtered Query
48 55
 class LeFilteredQuery(LeQuery):
49 56
 
57
+    ## @brief Constructor
58
+    # @param target_class EmClass : Object of the query
50 59
     def __init__(self, target_class):
51 60
         super().__init__(target_class)
52 61
 
62
+    ## @brief Validates the query filters
63
+    # @param query_filters list
64
+    # @return bool
65
+    # @raise LeQueryError if one of the filter is not valid
53 66
     @classmethod
54 67
     def validate_query_filters(cls, query_filters):
55 68
         for query_filter in query_filters:
56
-            if query_filter[1] not in cls._query_operators:
69
+            if query_filter[1] not in cls.query_operators:
57 70
                 raise LeQueryError("The operator %s is not valid." % query_filter[1])
58 71
         return True
59 72
 
73
+    ## @brief Checks if a field is relational
74
+    # @param field str : Name of the field
75
+    # @return bool
60 76
     @classmethod
61 77
     def is_relational_field(cls, field):
62 78
         return field.startswith('superior.') or field.startswith('subordinate.')
63 79
 
80
+
81
+## @brief Class representing a Get Query
64 82
 class LeGetQuery(LeFilteredQuery):
65 83
 
66
-    def __init__(self, target_class, target_uid, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
84
+    ## @brief Constructor
85
+    # @param target_class EmClass : main class
86
+    # @param query_filters
87
+    # @param field_list list
88
+    # @param order list : list of tuples corresponding to the fields used to sort the results
89
+    # @param group list : list of tuples corresponding to the fields used to group the results
90
+    # @param limit int : Maximum number of results to get
91
+    # @param offset int
92
+    # @param instanciate bool : if True, objects will be returned instead of dictionaries
93
+    def __init__(self, target_class, query_filters, field_list=None, order=None, group=None, limit=None, offset=0, instanciate=True):
67 94
         super().__init__(target_class)
68 95
         self.query_filters = query_filters
69 96
         self.default_field_list = []
70
-        self.field_list = field_list if field_list is not None else self._target_class.fieldnames()
97
+        self.field_list = field_list if field_list is not None else self.target_class.fieldnames()
71 98
         self.order = order
72 99
         self.group = group
73 100
         self.limit = limit
74 101
         self.offset = offset
75 102
         self.instanciate = instanciate
76
-        self.target_object = None  # TODO get an instance of the target_class using a unique id
77 103
 
104
+    ## @brief executes the query
105
+    # @return list
106
+    # @TODO activate LodelHook calls
78 107
     def execute(self):
79
-        datas = self.datas  # TODO : replace with LodelHook.call_hook('leapi_get_pre', self.target_object, self.datas)
108
+        datas = self.datas  # LodelHook.call_hook('leapi_get_pre', self.target_object, self.datas)
80 109
         ret = self.__get(**datas)
81 110
         # ret = LodelHook.call_hook('leapi_get_post', self.target_object, ret)
82 111
         return ret
83 112
 
84
-    def __get(self, **kwargs):
85
-        field_list = self.__prepare_field_list(self.field_list)  #TODO implement the prepare_field_list method
113
+    def __get(self, **datas):
114
+        field_list = self.__prepare_field_list(self.field_list)
86 115
 
87 116
         query_filters, relational_filters = self.__prepare_filters()
88 117
 
89
-        # Preparing order
118
+        # Preparing the "order" parameters
90 119
         if self.order:
91 120
             order = self.__prepare_order()
92 121
             if isinstance(order, Exception):
93
-                raise order  # can be buffered and raised later, but _prepare_filters raise when fails
122
+                raise order  # can be buffered and raised later, but __prepare_filters raise when fails
94 123
 
95
-        # Preparing group
124
+        # Preparing the "group" parameters
96 125
         if self.group:
97 126
             group = self.__prepare_order()
98 127
             if isinstance(group, Exception):
@@ -108,12 +137,15 @@ class LeGetQuery(LeFilteredQuery):
108 137
         results = self._datasource.select()  # TODO add the correct arguments for the datasource's method call
109 138
         return results
110 139
 
140
+    ## @brief prepares the field list
141
+    # @return list
142
+    # @raise LeApiDataCheckError
111 143
     def __prepare_field_list(self):
112 144
         errors = dict()
113 145
         ret_field_list = list()
114 146
         for field in self.field_list:
115 147
             if self.is_relational(field):
116
-                ret = self.__prepare_relational_fields(field)
148
+                ret = self.__prepare_relational_field(field)
117 149
             else:
118 150
                 ret = self.__check_field(field)
119 151
 
@@ -127,10 +159,15 @@ class LeGetQuery(LeFilteredQuery):
127 159
 
128 160
         return ret_field_list
129 161
 
130
-    def __prepare_relational_fields(self, field):
162
+    ## @brief prepares a relational field
163
+    def __prepare_relational_field(self, field):
131 164
         # TODO Implement the method
132 165
         return field
133 166
 
167
+    ## @brief splits the filter string into a tuple (FIELD, OPERATOR, VALUE)
168
+    # @param filter str
169
+    # @return tuple
170
+    # @raise ValueError
134 171
     def __split_filter(self, filter):
135 172
         if self.query_re is None:
136 173
             self.__compile_query_re()
@@ -153,11 +190,17 @@ class LeGetQuery(LeFilteredQuery):
153 190
         op_re_piece += ')'
154 191
         self.query_re = re.compile('^\s*(?P<field>(((superior)|(subordinate))\.)?[a-z_][a-z0-9\-_]*)\s*'+op_re_piece+'\s*(?P<value>[^<>=!].*)\s*$', flags=re.IGNORECASE)
155 192
 
156
-    def __check_field(self, target_object, field):
157
-        if field not in self.target_object.fieldnames():
158
-            return ValueError("No such field '%s' in %s" % (field, self.target_object.__class__))
193
+    ## @brief checks if a field is in the target class of the query
194
+    # @param field str
195
+    # @return str
196
+    # @raise ValueError
197
+    def __check_field(self, field):
198
+        if field not in self.target_class.fieldnames():
199
+            return ValueError("No such field '%s' in %s" % (field, self.target_class))
159 200
         return field
160 201
 
202
+    ## @brief Prepares the filters (relational and others)
203
+    # @return tuple
161 204
     def __prepare_filters(self):
162 205
         filters = list()
163 206
         errors = dict()
@@ -173,7 +216,7 @@ class LeGetQuery(LeFilteredQuery):
173 216
 
174 217
         for field, operator, value in filters:
175 218
             # TODO check the relation filters
176
-            ret = self.__check_field(self.target_object, field)
219
+            ret = self.__check_field(field)
177 220
             if isinstance(ret, Exception):
178 221
                 errors[field] = ret
179 222
             else:
@@ -188,9 +231,11 @@ class LeGetQuery(LeFilteredQuery):
188 231
         datas = dict()
189 232
         if LeFilteredQuery.validate_query_filters(self.query_filters):
190 233
             datas['query_filters'] = self.query_filters
191
-        datas['target_object'] = self.target_object
234
+        datas['target_class'] = self.target_class
192 235
         return datas
193 236
 
237
+    ## @brief prepares the "order" parameters
238
+    # @return list
194 239
     def __prepare_order(self):
195 240
         errors = dict()
196 241
         result = []
@@ -200,14 +245,14 @@ class LeGetQuery(LeFilteredQuery):
200 245
             if len(order_field) != 2 or order_field[1].upper() not in ['ASC', 'DESC']:
201 246
                 errors[order_field] = ValueError("Expected a string or a tuple with (FIELDNAME, ['ASC'|'DESC']) but got : %s" % order_field)
202 247
             else:
203
-                ret = self._target_class.check_field(order_field[0])
248
+                ret = self.target_class.check_field(order_field[0])
204 249
                 if isinstance(ret, Exception):
205 250
                     errors[order_field] = ret
206 251
             order_field = (order_field[0], order_field[1].upper())
207 252
             result.append(order_field)
208 253
 
209 254
         if len(errors) > 0:
210
-            return LeApiErrors("Errors when preparing ordering fields", errors)
255
+            raise LeApiErrors("Errors when preparing ordering fields", errors)
211 256
         return result
212 257
 
213 258
 
@@ -217,7 +262,6 @@ class LeUpdateQuery(LeFilteredQuery):
217 262
         super().__init__(target_class)
218 263
         self.query_filters = query_filters
219 264
         self.target_uid = target_uid
220
-        self.target_object = None  # TODO get an instance of the target_class using a unique id
221 265
 
222 266
     def execute(self):
223 267
         # LodelHook.call_hook('leapi_update_pre', self.target_object, None)
@@ -230,7 +274,7 @@ class LeUpdateQuery(LeFilteredQuery):
230 274
     # @TODO change the behavior in case of error in the update process
231 275
     def __update(self):
232 276
         updated_datas = self.__prepare()
233
-        ret = self._datasource.update(self.target_uid, **updated_datas)  # TODO add the correct arguments for the datasource's method call
277
+        ret = self.datasource.update(self.target_uid, **updated_datas)  # TODO add the correct arguments for the datasource's method
234 278
         if ret == 1:
235 279
             return True
236 280
         else:
@@ -276,4 +320,4 @@ class LeDeleteQuery(LeFilteredQuery):
276 320
         datas['target_uid'] = self.target_uid
277 321
         datas['target_class'] = self._target_class
278 322
 
279
-        return datas
323
+        return datas

+ 72
- 0
lodel/plugins.py Voir le fichier

@@ -0,0 +1,72 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import os.path
4
+
5
+from importlib.machinery import SourceFileLoader, SourcelessFileLoader
6
+
7
+## @package lodel.plugins Lodel2 plugins management
8
+#
9
+# Lodel2 plugins are stored in directories
10
+# A typicall lodel2 plugin directory structure looks like :
11
+# - {{__init__.py}}} containing informations like full_name, authors, licence etc.
12
+# - main.py containing hooks registration etc
13
+# - confspec.py containing a configuration specification dictionary named CONFSPEC
14
+
15
+VIRTUAL_PACKAGE_NAME = 'lodel.plugins_pkg'
16
+CONFSPEC_NAME = 'confspec.py'
17
+
18
+class Plugins(object):
19
+    
20
+    ## @brief Stores plugin directories paths
21
+    _plugin_directories = None
22
+    ## @brief Optimisation cache storage for plugin paths
23
+    _plugin_paths = dict()
24
+
25
+    def __init__(self):
26
+        self.started()
27
+    
28
+    ## @brief Given a plugin name returns the plugin path
29
+    # @param plugin_name str : The plugin name
30
+    # @return the plugin directory path
31
+    @classmethod
32
+    def plugin_path(cls, plugin_name):
33
+        cls.started()
34
+        try:
35
+            return cls._plugin_paths[plugin_name]
36
+        except KeyError:
37
+            pass
38
+        
39
+        path = None
40
+        for cur_path in cls._plugin_directories:
41
+            plugin_path = os.path.join(cur_path, plugin_name)+'/'
42
+            print(plugin_path)
43
+            if os.path.isdir(plugin_path):
44
+                return plugin_path
45
+        raise NameError("No plugin named '%s'" % plugin_name)
46
+
47
+    ## @brief Fetch a confspec given a plugin_name
48
+    # @param plugin_name str : The plugin name
49
+    # @return a dict of conf spec
50
+    @classmethod
51
+    def get_confspec(cls, plugin_name):
52
+        cls.started()
53
+        plugin_path = cls.plugin_path(plugin_name)
54
+        plugin_module = '%s.%s' % ( VIRTUAL_PACKAGE_NAME,
55
+                                    plugin_name)
56
+        conf_spec_module = plugin_module + '.confspec'
57
+        
58
+        conf_spec_source = plugin_path + CONFSPEC_NAME
59
+
60
+        loader = SourceFileLoader(conf_spec_module, conf_spec_source)
61
+        confspec_module = loader.load_module()
62
+        return getattr(confspec_module, 'CONFSPEC')
63
+
64
+    @classmethod
65
+    def bootstrap(cls, plugins_directories):
66
+        cls._plugin_directories = plugins_directories
67
+
68
+    @classmethod
69
+    def started(cls, raise_if_not = True):
70
+        res = cls._plugin_directories is not None
71
+        if raise_if_not and not res:
72
+            raise RuntimeError("Class Plugins is not initialized")

+ 0
- 145
lodel/settings.py Voir le fichier

@@ -1,145 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-
3
-import types
4
-import warnings
5
-from . import settings_format
6
-
7
-## @package Lodel.settings
8
-#
9
-# @brief Defines stuff to handles Lodel2 configuration (see @ref lodel_settings )
10
-#
11
-# To access the confs use the Lodel.settings.Settings SettingsHandler instance
12
-
13
-## @brief A class designed to handles Lodel2 settings
14
-#
15
-# When instanciating a SettingsHandler, the new instance is filled with the content of settings.py (in the root directory of lodel2
16
-#
17
-# @warning You don't have to instanciate this class, you can access to the global instance with the Settings variable in this module
18
-# @todo broken stuff... Rewrite it
19
-# @todo Forbid module assignement in settings ! and disable tests about this
20
-# @todo Implements a type checking of config value
21
-# @todo Implements default values for config keys
22
-class SettingsHandler(object):
23
-    
24
-    ## @brief Shortcut
25
-    _allowed = settings_format.ALLOWED + settings_format.MANDATORY
26
-    ## @brief Shortcut
27
-    _mandatory = settings_format.MANDATORY
28
-
29
-    def __init__(self):
30
-        try:
31
-            import settings as default_settings
32
-            self._load_module(default_settings)
33
-        except ImportError:
34
-            warnings.warn("Unable to find global default settings")
35
-
36
-        ## @brief A flag set to True when the instance is fully loaded
37
-        self._set_loaded(False if len(self._missings()) > 0 else True)
38
-    
39
-    ## @brief Compat wrapper for getattr
40
-    def get(self, name):
41
-        return getattr(self, name)
42
-    
43
-    ## @brief Compat wrapper for setattr
44
-    def set(self, name, value):
45
-        return setattr(self, name, value)
46
-
47
-    ## @brief Load every module properties in the settings instance
48
-    #
49
-    # Load a module content into a SettingsHandler instance and checks that no mandatory settings are missing
50
-    # @note Example : <pre> import my_cool_settings;
51
-    # Settings._load_module(my_cool_settings);</pre>
52
-    # @param module module|None: a loaded module (if None just check for missing settings)
53
-    # @throw LookupError if invalid settings found or if mandatory settings are missing
54
-    def load_module(self, module = None):
55
-        if not(module is None):
56
-            self._load_module(module)
57
-        missings = self._missings()
58
-        if len(missings) > 0:
59
-            self._loaded = False
60
-            raise LookupError("Mandatory settings are missing : %s"%missings)
61
-        self._set_loaded(True)
62
-    
63
-    ## @brief supersede of default __setattr__ method
64
-    def __setattr__(self, name, value):
65
-        if not hasattr(self, name):
66
-            if name not in self._allowed:
67
-                raise LookupError("Invalid setting : %s"%name)
68
-        super().__setattr__(name, value)
69
-
70
-    ## @brief This method do the job for SettingsHandler.load_module()
71
-    #
72
-    # @note The difference with SettingsHandler.load_module() is that it didn't check if some settings are missing
73
-    # @throw LokkupError if an invalid settings is given
74
-    # @param module : a loaded module
75
-    def _load_module(self, module):
76
-        errors = []
77
-        fatal_errors = []
78
-        conf_dict = {
79
-            name: getattr(module, name)
80
-            for name in dir(module) 
81
-            if not name.startswith('__') and not isinstance(getattr(module, name), types.ModuleType)
82
-        }
83
-        for name, value in conf_dict.items():
84
-            try:
85
-                setattr(self, name, value)
86
-            except LookupError:
87
-                errors.append(name)
88
-        if len(errors) > 0:
89
-            err_msg = "Found invalid settings in %s : %s"%(module.__name__, errors)
90
-            raise LookupError(err_msg)
91
-
92
-    ## @brief Refresh the allowed and mandatory settings list
93
-    @classmethod
94
-    def _refresh_format(cls):
95
-        ## @brief Shortcut
96
-        cls._allowed = settings_format.ALLOWED + settings_format.MANDATORY
97
-        ## @brief Shortcut
98
-        cls._mandatory = settings_format.MANDATORY
99
-
100
-    ## @brief If some settings are missings return their names
101
-    # @return an array of string
102
-    def _missings(self):
103
-        return [ confname for confname in self._mandatory if not hasattr(self, confname) ]
104
-
105
-    def _set_loaded(self, value):
106
-        super().__setattr__('_loaded', bool(value))
107
-
108
-Settings = SettingsHandler()
109
-
110
-## @page lodel_settings Lodel SettingsHandler
111
-#
112
-# This page describe the way settings are handled in Lodel2.
113
-#
114
-# @section lodel_settings_files Lodel settings files
115
-#
116
-# - Lodel/settings.py defines the Lodel.settings package, the SettingsHandler class and the Lodel.settings.Settings instance
117
-# - Lodel/settings_format.py defines the mandatory and allowed configurations keys lists
118
-# - install/instance_settings.py is a model of the file that will be deployed in Lodel2 instances directories
119
-#
120
-# @section Using Lodel.settings.Settings SettingsHandler instance
121
-#
122
-# @subsection lodel_settings_without_loader Without loader
123
-#
124
-# Without any loader you can import Lodel.settings.Settings and acces its property with getattr (or . ) or with SettingsHandler.get() method.
125
-# In the same way you can set a settings by standart affectation of a propery or with SettingsHandler.set() method.
126
-#
127
-# @subsection lodel_settings_loader With a loader in a lodel2 instance
128
-#
129
-# The loader will import Lodel.settings.Settings and then calls the SettingsHandler.load_module() method to load the content of the instance_settings.py file into the SettingsHandler instance
130
-#
131
-# @subsection lodel_settings_example Examples
132
-#
133
-# <pre>
134
-# #!/usr/bin/python
135
-# from Lodel.settings import Settings
136
-# if Settings.debug:
137
-#   print("DEBUG")
138
-# # or
139
-# if Settings.get('debug'):
140
-#   print("DEBUG")
141
-# Settings.debug = False
142
-# # or
143
-# Settings.set('debug', False)
144
-# </pre>
145
-# 

+ 102
- 0
lodel/settings/settings.py Voir le fichier

@@ -0,0 +1,102 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import sys
4
+import os
5
+import configparser
6
+
7
+from lodel.plugins import Plugins
8
+from lodel.settings.utils import SettingsError, SettingsErrors
9
+from lodel.settings.validator import SettingValidator
10
+from lodel.settings.settings_loader import SettingsLoader
11
+
12
+## @package lodel.settings Lodel2 settings package
13
+#
14
+# Contains all module that help handling settings
15
+
16
+## @package lodel.settings.settings Lodel2 settings module
17
+#
18
+# Handles configuration load/parse/check.
19
+#
20
+# @subsection Configuration load process
21
+#
22
+# The configuration load process is not trivial. In fact loaded plugins are able to add their own options.
23
+# But the list of plugins to load and the plugins options are in the same file, the instance configuration file.
24
+#
25
+# @subsection Configuration specification
26
+#
27
+# Configuration specification is divided in 2 parts :
28
+# - default values
29
+# - value validation/cast (see @ref Lodel.settings.validator.ConfValidator )
30
+# 
31
+
32
+PYTHON_SYS_LIB_PATH = '/usr/local/lib/python{major}.{minor}/'.format(
33
+
34
+                                                                        major = sys.version_info.major,
35
+                                                                        minor = sys.version_info.minor)
36
+## @brief Handles configuration load etc.
37
+class Settings(object):
38
+    
39
+    ## @brief global conf specsification (default_value + validator)
40
+    _conf_preload = {
41
+            'lib_path': (   PYTHON_SYS_LIB_PATH+'/lodel2/',
42
+                            SettingValidator('directory')),
43
+            'plugins_path': (   PYTHON_SYS_LIB_PATH+'lodel2/plugins/',
44
+                                SettingValidator('directory_list')),
45
+    }
46
+    
47
+    def __init__(self, conf_file = '/etc/lodel2/lodel2.conf', conf_dir = 'conf.d'):
48
+        self.__confs = dict()
49
+        
50
+        self.__load_bootstrap_conf(conf_file)
51
+        # now we should have the self.__confs['lodel2']['plugins_paths'] and
52
+        # self.__confs['lodel2']['lib_path'] set
53
+        self.__bootstrap()
54
+    
55
+    ## @brief This method handlers Settings instance bootstraping
56
+    def __bootstrap(self):
57
+        #loader = SettingsLoader(self.__conf_dir)
58
+
59
+        # Starting the Plugins class
60
+        Plugins.bootstrap(self.__confs['lodel2']['plugins_path'])
61
+        specs = Plugins.get_confspec('dummy')
62
+        print("Got specs : %s " % specs)
63
+        
64
+        # then fetch options values from conf specs
65
+    
66
+    ## @brief Load base global configurations keys
67
+    #
68
+    # Base configurations keys are :
69
+    # - lodel2 lib path
70
+    # - lodel2 plugins path
71
+    #
72
+    # @note return nothing but set the __confs attribute
73
+    # @see Settings._conf_preload
74
+    def __load_bootstrap_conf(self, conf_file):
75
+        config = configparser.ConfigParser()
76
+        config.read(conf_file)
77
+        sections = config.sections()
78
+        if len(sections) != 1 or sections[0].lower() != 'lodel2':
79
+            raise SettingsError("Global conf error, expected lodel2 section not found")
80
+        
81
+        #Load default values in result
82
+        res = dict()
83
+        for keyname, (keyvalue, validator) in self._conf_preload.items():
84
+            res[keyname] = keyvalue
85
+
86
+        confs = config[sections[0]]
87
+        errors = []
88
+        for name in confs:
89
+            if name not in res:
90
+                errors.append(  SettingsError(
91
+                                    "Unknow field",
92
+                                    "lodel2.%s" % name,
93
+                                    conf_file))
94
+            try:
95
+                res[name] = self._conf_preload[name][1](confs[name])
96
+            except Exception as e:
97
+                errors.append(SettingsError(str(e), name, conf_file))
98
+        if len(errors) > 0:
99
+            raise SettingsErrors(errors)
100
+        
101
+        self.__confs['lodel2'] = res
102
+

+ 19
- 0
lodel/settings/utils.py Voir le fichier

@@ -22,4 +22,23 @@ class SettingsError(Exception):
22 22
 
23 23
         res += ": %s" % (self.__msg)
24 24
         return res
25
+
26
+## @brief Designed to handles mutliple SettingsError
27
+class SettingsErrors(Exception):
28
+    
29
+    ## @brief Instanciate an SettingsErrors
30
+    # @param exceptions list : list of SettingsError instance
31
+    def __init__(self, exceptions):
32
+        for expt in exceptions: 
33
+            if not isinstance(expt, SettingsError):
34
+                raise ValueError("The 'exceptions' argument has to be an array of <class SettingsError>, but a %s was found in the list" % type(expt))
35
+        self.__exceptions = exceptions
25 36
         
37
+
38
+    def __repr__(self): return str(self)
39
+
40
+    def __str__(self):
41
+        res = "Errors :\n"
42
+        for expt in self.__exceptions:
43
+            res += "\t%s\n" % str(expt)
44
+        return res

+ 181
- 0
lodel/settings/validator.py Voir le fichier

@@ -0,0 +1,181 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import sys
4
+import os.path
5
+import re
6
+import inspect
7
+import copy
8
+
9
+## @package lodel.settings.validator Lodel2 settings validators/cast module
10
+#
11
+# Validator are registered in the SettingValidator class.
12
+
13
+class SettingsValidationError(Exception):
14
+    pass
15
+
16
+## @brief Handles settings validators
17
+#
18
+# Class instance are callable objects that takes a value argument (the value to validate). It raises
19
+# a SettingsValidationError if validation fails, else it returns a properly
20
+# casted value.
21
+class SettingValidator(object):
22
+    
23
+    _validators = dict()
24
+    _description = dict()
25
+    
26
+    ## @brief Instanciate a validator
27
+    def __init__(self, name, none_is_valid = False):
28
+        if name is not None and name not in self._validators:
29
+            raise NameError("No validator named '%s'" % name)
30
+        self.__name = name
31
+
32
+    ## @brief Call the validator
33
+    # @param value *
34
+    # @return properly casted value
35
+    # @throw SettingsValidationError
36
+    def __call__(self, value):
37
+        if self.__name is None:
38
+            return value
39
+        try:
40
+            return self._validators[self.__name](value)
41
+        except Exception as e:
42
+            raise SettingsValidationError(e)
43
+    
44
+    ## @brief Register a new validator
45
+    # @param name str : validator name
46
+    # @param callback callable : the function that will validate a value
47
+    @classmethod
48
+    def register_validator(cls, name, callback, description=None):
49
+        if name in cls._validators:
50
+            raise NameError("A validator named '%s' allready exists" % name)
51
+        # Broken test for callable
52
+        if not inspect.isfunction(callback) and not inspect.ismethod(callback) and not hasattr(callback, '__call__'):
53
+            raise TypeError("Callable expected but got %s" % type(callback))
54
+        cls._validators[name] = callback
55
+        cls._description[name] = description
56
+    
57
+    ## @brief Get the validator list associated with description
58
+    @classmethod
59
+    def validators_list(cls):
60
+        return copy.copy(cls._description)
61
+
62
+    ## @brief Create and register a list validator
63
+    # @param elt_validator callable : The validator that will be used for validate each elt value
64
+    # @param validator_name str
65
+    # @param description None | str
66
+    # @param separator str : The element separator
67
+    # @return A SettingValidator instance
68
+    @classmethod
69
+    def create_list_validator(cls, validator_name, elt_validator, description = None, separator = ','):
70
+        def list_validator(value):
71
+            res = list()
72
+            errors = list()
73
+            for elt in value.split(separator):
74
+                res.append(elt_validator(elt))
75
+            return res
76
+        description = "Convert value to an array" if description is None else description
77
+        cls.register_validator(
78
+                                validator_name,
79
+                                list_validator,
80
+                                description)
81
+        return cls(validator_name)
82
+                
83
+    ## @brief Create and register a regular expression validator
84
+    # @param pattern str : regex pattern
85
+    # @param validator_name str : The validator name
86
+    # @param description str : Validator description
87
+    # @return a SettingValidator instance
88
+    @classmethod
89
+    def create_re_validator(cls, pattern, validator_name, description = None):
90
+        def re_validator(value):
91
+            if not re.match(pattern, value):
92
+                raise SettingsValidationError("The value '%s' doesn't match the following pattern '%s'" % pattern)
93
+            return value
94
+        #registering the validator
95
+        cls.register_validator(
96
+                                validator_name,
97
+                                re_validator,
98
+                                ("Match value to '%s'" % pattern) if description is None else description)
99
+        return cls(validator_name)
100
+
101
+    
102
+    ## @return a list of registered validators
103
+    def validators_list_str(cls):
104
+        result = ''
105
+        for name in cls._validators:
106
+            result += "\t%s" % name
107
+            if name in self._description and self._description[name] is not None:
108
+                result += "\t: %s" % self._description[name]
109
+            result += "\n"
110
+        return result
111
+
112
+## @brief Integer value validator callback
113
+def int_val(value):
114
+    return int(value)
115
+
116
+## @brief Output file validator callback
117
+# @return A file object (if filename is '-' return sys.stderr)
118
+def file_err_output(value):
119
+    if not isinstance(value, str):
120
+        raise SettingsValidationError("A string was expected but got '%s' " % value)
121
+    if value == '-':
122
+        return sys.stderr
123
+    return value
124
+
125
+## @brief Boolean value validator callback
126
+def boolean_val(value):
127
+    if not (value is True) and not (value is False):
128
+        raise SettingsValidationError("A boolean was expected but got '%s' " % value)
129
+    return bool(value)
130
+
131
+def directory_val(value):
132
+    res = SettingValidator('strip')(value)
133
+    if not os.path.isdir(res):
134
+        raise SettingsValidationError("Folowing path don't exists or is not a directory : '%s'"%res)
135
+    return res
136
+
137
+#
138
+#   Default validators registration
139
+#
140
+
141
+SettingValidator.register_validator(
142
+                                        'strip',
143
+                                        str.strip,
144
+                                        'String trim')
145
+
146
+SettingValidator.register_validator(
147
+                                        'int',
148
+                                        int_val,
149
+                                        'Integer value validator')
150
+
151
+SettingValidator.register_validator(
152
+                                        'bool',
153
+                                        boolean_val,
154
+                                        'Boolean value validator')
155
+
156
+SettingValidator.register_validator(
157
+                                        'errfile',
158
+                                        file_err_output,
159
+                                        'Error output file validator (return stderr if filename is "-")')
160
+
161
+SettingValidator.register_validator(
162
+                                        'directory',
163
+                                        directory_val,
164
+                                        'Directory path validator')
165
+
166
+SettingValidator.create_list_validator(
167
+                                            'list',
168
+                                            SettingValidator('strip'),
169
+                                            description = "Simple list validator. Validate a list of values separated by ','",
170
+                                            separator = ',')
171
+
172
+SettingValidator.create_list_validator(
173
+                                            'directory_list',
174
+                                            SettingValidator('directory'),
175
+                                            description = "Validator for a list of directory path separated with ','",
176
+                                            separator = ',')
177
+
178
+SettingValidator.create_re_validator(
179
+                                        r'^https?://[^\./]+.[^\./]+/?.*$',
180
+                                        'http_url',
181
+                                        'Url validator')

+ 0
- 26
lodel/settings_format.py Voir le fichier

@@ -1,26 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-## @package Lodel.settings_format Rules for settings
3
-
4
-## @brief List mandatory configurations keys
5
-MANDATORY = [
6
-    'debug',
7
-    'debug_sql',
8
-    'sitename',
9
-    'lodel2_lib_path',
10
-    'em_file',
11
-    'dynamic_code_file',
12
-    'ds_package',
13
-    'datasource',
14
-    'mh_classname',
15
-    'migration_options',
16
-    'base_path',
17
-    'plugins',
18
-    'logging',
19
-]
20
-
21
-## @brief List allowed (but not mandatory) configurations keys
22
-ALLOWED = [
23
-    'em_graph_output',
24
-    'em_graph_format',
25
-    'templates_base_dir'
26
-]

+ 7
- 0
plugins/dummy/confspec.py Voir le fichier

@@ -0,0 +1,7 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+CONFSPEC = {
4
+    'section1': {
5
+        'key1': None
6
+    }
7
+}

+ 3
- 0
settings.ini Voir le fichier

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

Loading…
Annuler
Enregistrer