Browse Source

Beginin leapi implementation

Yann Weber 8 years ago
parent
commit
c7ef7d471b
3 changed files with 271 additions and 1 deletions
  1. 3
    1
      lodel/leapi/datahandlers/field_data_handler.py
  2. 227
    0
      lodel/leapi/leobject.py
  3. 41
    0
      lodel/leapi/query.py

+ 3
- 1
lodel/leapi/datahandlers/field_data_handler.py View File

@@ -57,6 +57,8 @@ class FieldDataHandler(object):
57 57
     # @param fname str : The field name
58 58
     # @param datas dict : dict storing fields values (from the component)
59 59
     # @param cur_value : the value from the current field (identified by fieldname)
60
+    # @return the value
61
+    # @throw RunTimeError if data construction fails
60 62
     def construct_data(self, emcomponent, fname, datas, cur_value):
61 63
         emcomponent_fields = emcomponent.fields()
62 64
         fname_data_handler = None
@@ -70,7 +72,7 @@ class FieldDataHandler(object):
70 72
         elif fname_data_handler is not None and fname_data_handler.nullable:
71 73
                 return None
72 74
 
73
-        raise RuntimeError("Unable to construct data for field %s", fname)
75
+        return RuntimeError("Unable to construct data for field %s", fname)
74 76
 
75 77
     ## @brief Check datas consistency
76 78
     # @param emcomponent EmComponent : An EmComponent child class instance

+ 227
- 0
lodel/leapi/leobject.py View File

@@ -0,0 +1,227 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+class LeApiErrors(Exception):
4
+    ## @brief Instanciate a new exceptions handling multiple exceptions
5
+    # @param msg str : Exception message
6
+    # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
7
+    def __init__(self, msg = "Unknow error", exceptions = None):
8
+        self._msg = msg
9
+        self._exceptions = dict() if exceptions is None else exceptions
10
+
11
+    def __repr__(self):
12
+        return self.__str__()
13
+
14
+    def __str__(self):
15
+        msg = self._msg
16
+        for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
17
+        for obj, expt in for_iter:
18
+            msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
19
+                    expt_obj = obj,
20
+                    expt_name=expt.__class__.__name__,
21
+                    expt_msg=str(expt)
22
+            )
23
+        return msg
24
+
25
+
26
+## @brief When an error concern a query
27
+class LeApiQueryError(LeApiErrors): pass
28
+
29
+
30
+## @brief When an error concerns a datas
31
+class LeApiDataCheckError(LeApiErrors): pass
32
+
33
+
34
+## @brief Wrapper class for LeObject getter & setter
35
+#
36
+# This class intend to provide easy & friendly access to LeObject fields values 
37
+# without name collision problems
38
+# @note Wrapped methods are : LeObject.data() & LeObject.set_data()
39
+class LeObjectValues(object):
40
+    
41
+    ## @brief Construct a new LeObjectValues
42
+    # @param set_callback method : The LeObject.set_datas() method of corresponding LeObject class
43
+    # @param get_callback method : The LeObject.get_datas() method of corresponding LeObject class
44
+    def __init__(self, fieldnames_callback, set_callback, get_callback):
45
+        self.__setter = set_callback
46
+        self.__getter = get_callback
47
+    
48
+    ## @brief Provide read access to datas values
49
+    # @note Read access should be provided for all fields
50
+    # @param fname str : Field name
51
+    def __getattribute__(self, fname):
52
+        return self.__getter(fname)
53
+    
54
+    ## @brief Provide write access to datas values
55
+    # @note Write acces shouldn't be provided for internal or immutable fields
56
+    # @param fname str : Field name
57
+    # @param fval * : the field value
58
+    def __setattribute__(self, fname, fval):
59
+        return self.__setter(fname, fval)
60
+        
61
+
62
+class LeObject(object):
63
+    
64
+    ## @brief A dict that stores DataHandler instances indexed by field name
65
+    __fields = None
66
+    ## @brief A tuple of fieldname (or a uniq fieldname) representing uid
67
+    __uid = None 
68
+
69
+    def __init__(self, **kwargs):
70
+        ## @brief A dict that stores fieldvalues indexed by fieldname
71
+        self.__datas = dict(fname:None for fname in self.__fields)
72
+        ## @brief Store a list of initianilized fields when instanciation not complete else store True
73
+        self.__initialized = list()
74
+        ## @brief Datas accessor. Instance of @ref LeObjectValues
75
+        self.d = LeObjectValues(self.fieldnames, self.set_data, self.data)
76
+
77
+        # Checks that uid is given
78
+        for uid_name in self.__uid:
79
+            if uid_name not in kwargs:
80
+                raise AttributeError("Cannot instanciate a LeObject without it's identifier")
81
+            self.__datas[uid_name] = kwargs[uid_name]
82
+            del(kwargs[uid_name])
83
+            self.__initialized.append(uid_name)
84
+
85
+        allowed_fieldnames = self.fieldnames(include_ro = False)
86
+        err_list = list()
87
+        for fieldname, fieldval in kwargs.items():
88
+            if fieldname not in allowed_fieldnames:
89
+                if fieldname in self.__fields:
90
+                    err_list.append(
91
+                        AttributeError("Value given for internal field : '%s'" % fieldname)
92
+                    )
93
+                else:
94
+                    err_list.append(
95
+                        AttributeError("Unknown fieldname : '%s'" % fieldname)
96
+                    )
97
+            else:
98
+                self.__datas[fieldame] = fieldval
99
+                self.__initialized = list()
100
+        self.set_initialized()
101
+    
102
+    #-----------------------------------#
103
+    #   Fields datas handling methods   #
104
+    #-----------------------------------#
105
+
106
+    ## @brief @property True if LeObject is initialized else False
107
+    @property
108
+    def initialized(self):
109
+        return not isinstance(self.__initialized, list)
110
+
111
+    ## @brief Return a list of fieldnames
112
+    # @param include_ro bool : if True include read only field names
113
+    # @return a list of str
114
+    @classmethod
115
+    def fieldnames(cls, include_ro = False):
116
+        if not include_ro:
117
+            return [ fname for fname in self.__fields if not self.__fields[fname].is_internal() ]
118
+        else:
119
+            return list(self.__fields.keys())
120
+ 
121
+    ## @brief Read only access to all datas
122
+    # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
123
+    # @param name str : field name
124
+    # @return the Value
125
+    # @throw RuntimeError if the field is not initialized yet
126
+    # @throw NameError if name is not an existing field name
127
+    def data(self, field_name):
128
+        if field_name not in self.__fields.keys():
129
+            raise NameError("No such field in %s : %s" % (self.__class__.__name__, name)
130
+        if not self.initialized and name not in self.__initialized:
131
+            raise RuntimeError("The field %s is not initialized yet (and have no value)" % name)
132
+        return self.__datas[name]
133
+    
134
+    ## @brief Datas setter
135
+    # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
136
+    # @param fname str : field name
137
+    # @param fval * : field value
138
+    # @return the value that is really set
139
+    # @throw NameError if fname is not valid
140
+    # @throw AttributeError if the field is not writtable
141
+    def set_data(self, fname, fval):
142
+        if field_name not in self.fieldnames(include_ro = False):
143
+            if field_name not in self.__fields.keys():
144
+                raise NameError("No such field in %s : %s" % (self.__class__.__name__, name))
145
+            else:
146
+                raise AttributeError("The field %s is read only" % fname)
147
+        self.__datas[fname] = fval
148
+        if not self.initialized and fname not in self.__initialized:
149
+            # Add field to initialized fields list
150
+            self.__initialized.append(fname)
151
+            self.__set_initialized()
152
+        if self.initialized:
153
+            # Running full value check
154
+            ret = self.__check_modified_values()
155
+            if ret is None:
156
+                return self.__datas[fname]
157
+            else:
158
+                raise LeApiErrors("Data check error", ret)
159
+        else:
160
+            # Doing value check on modified field
161
+            # We skip full validation here because the LeObject is not fully initialized yet
162
+            val, err = self.__fields[fname].check_data_value(fval)
163
+            if isinstance(err, Exception):
164
+                #Revert change to be in valid state
165
+                del(self.__datas[fname])
166
+                del(self.__initialized[-1])
167
+                raise LeApiErrors("Data check error", {fname:err})
168
+            else:
169
+                self.__datas[fname] = val
170
+    
171
+    ## @brief Update the __initialized attribute according to LeObject internal state
172
+    #
173
+    # Check the list of initialized fields and set __initialized to True if all fields initialized
174
+    def __set_initialized(self):
175
+        if isinstance(self.__initialized, list):
176
+            expected_fields = self.fieldnames(include_ro = False) + self.__uid
177
+            if set(expected_fields) == set(self.__initialized):
178
+                self.__initialized = True
179
+
180
+    ## @brief Designed to be called when datas are modified
181
+    #
182
+    # Make different checks on the LeObject given it's state (fully initialized or not)
183
+    # @return None if checks succeded else return an exception list
184
+    def __check_modified_values(self):
185
+        err_list = dict()
186
+        if self.__initialized is True:
187
+            # Data value check
188
+            for fname in self.fieldnames(include_ro = False):
189
+                val, err = self.__fields[fname].check_data_value(self.__datas[fname])
190
+                if err is not None:
191
+                    err_list[fname] = err
192
+                else:
193
+                    self.__datas[fname] = val
194
+            # Data construction
195
+            if len(err_list) == 0:
196
+                for fname in self.fieldnames(include_ro = True):
197
+                    try:
198
+                        field = self.__fields[fname]
199
+                        self.__datas[fname] = fields.construct_data(    self,
200
+                                                                        fname,
201
+                                                                        self.__datas,
202
+                                                                        self.__datas[fname]
203
+                        )
204
+                    except Exception as e:
205
+                        err_list[fname] = e
206
+            # Datas consistency check
207
+            if len(err_list) == 0:
208
+                for fname in self.fieldnames(include_ro = True):
209
+                    field = self.__fields[fname]
210
+                    ret = field.check_data_consistency(self, fname, self.__datas)
211
+                    if isinstance(ret, Exception):
212
+                        err_list[fname] = ret
213
+        else:
214
+            # Data value check for initialized datas
215
+            for fname in self.__initialized:
216
+                val, err = self.__fields[fname].check_data_value(self.__datas[fname])
217
+                if err is not None:
218
+                    err_list[fname] = err
219
+                else:
220
+                    self.__datas[fname] = val
221
+        return err_list if len(err_list) > 0 else None
222
+
223
+    #-------------------#
224
+    #   Crud methods    #
225
+    #-------------------#
226
+
227
+    

+ 41
- 0
lodel/leapi/query.py View File

@@ -0,0 +1,41 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from leobject import LeObject
4
+
5
+class LeQueryError(Exception):
6
+    pass
7
+
8
+## @brief Handle CRUD operations on datasource
9
+class LeQuery(object):
10
+    
11
+    ## @brief Constructor
12
+    # @param target_class LeObject class or childs : The LeObject child class concerned by this query
13
+    def __init__(self, target_class):
14
+        if not issubclass(target_class, LeObject):
15
+            raise TypeError("target_class have to be a child class of LeObject")
16
+        self._target_class = target_class
17
+
18
+## @brief Handles insert queries
19
+class LeInsertQuery(LeQuery):
20
+
21
+    def __init__(self, target_class):
22
+        super().__init__(target_class)
23
+        if target_class.is_abstract():
24
+            raise LeQueryError("Target EmClass cannot be abstract for an InsertQuery")
25
+
26
+## @brief Handles Le*Query with a query_filter argument
27
+# @see LeGetQuery, LeUpdateQuery, LeDeleteQuery
28
+class LeFilteredQuery(LeQuery):
29
+    pass
30
+
31
+## @brief Handles Get queries
32
+class LeGetQuery(LeFilteredQuery):
33
+    pass   
34
+
35
+## @brief Handles Update queries
36
+class LeUpdateQuery(LeFilteredQuery):
37
+    pass
38
+
39
+## @biref Handles Delete queries
40
+class LeInsertQuery(LeFilteredQuery):
41
+    pass

Loading…
Cancel
Save