Salome HOME
50250f0a8622f82723530c74bf0b40058d6e3ded
[modules/kernel.git] / src / KERNEL_PY / kernel / datamodeler.py
1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2010-2023  CEA, EDF, OPEN CASCADE
3 #
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
8 #
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 # Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17 #
18 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
19 #
20
21 ## \defgroup datamodeler datamodeler
22 #  \{ 
23 #  \details Helper for modeling user data
24 #  \}
25
26 __author__="gboulant"
27 __date__ ="$15 avr. 2010 19:44:17$"
28
29 from .uiexception import DevelException
30
31 # Most usable class types
32 TypeString= "".__class__
33 __ref_integer = 0
34 TypeInteger = __ref_integer.__class__
35 __ref_double = 0.0
36 TypeDouble = __ref_double.__class__
37 __ref_list = []
38 TypeList = __ref_list.__class__
39 __ref_dict = {}
40 TypeDictionnary = __ref_dict.__class__
41
42 # There is no control to do for these attributes. They are attributes for the
43 # class management and not data of the model.
44 UNCHECKED_ATTRIBUTES = [
45     "_typemap",
46     "_rangemap",
47     "_defaultmap",
48     "_voidmap",
49 ]
50
51 ## This class is a placeholder for modeling data. An object based on this class
52 #  (particular instance or specialized derived class) can defined attributes with
53 #  the following properties:
54 #  - a type : the class or the type of the attribute. Setting an attribute to
55 #    a value whose type is not the specified type raises an exception.
56 #  - a range : a list of the possible values for the attribute. Setting an
57 #    attribute to a value not in the range raises an exception
58 #  - a default: the default value of an attribute when an instance is created
59 #  - a void flag: the attribute can be authorized to be None or not using this
60 #    flag. Setting an attribute to a None value while the flag is not set to
61 #    True raises an exception. By default, a None value is not allowed.
62 #
63 #  These properties are dictionnaries mapping the attribute name to its
64 #  associated value for the property.
65 #  \n A typical usage is to derived this class in a specialized form where the
66 #  attributes names and there properties are defined in the constructor. See
67 #  use cases at the end of this file.
68 #  \ingroup datamodeler
69 class DataModeler:
70     """
71     This class is a placeholder for modeling data. An object based on this class
72     (particular instance or specialized derived class) can defined attributes with
73     the following properties:
74
75     - a type : the class or the type of the attribute. Setting an attribute to
76       a value whose type is not the specified type raises an exception.
77     - a range : a list of the possible values for the attribute. Setting an
78       attribute to a value not in the range raises an exception
79     - a default: the default value of an attribute when an instance is created
80     - a void flag: the attribute can be authorized to be None or not using this
81       flag. Setting an attribute to a None value while the flag is not set to
82       True raises an exception. By default, a None value is not allowed.
83
84     These properties are dictionnaries mapping the attribute name to its
85     associated value for the property.
86
87     A typical usage is to derived this class in a specialized form where the
88     attributes names and there properties are defined in the constructor. See
89     use cases at the end of this file.
90
91     """
92     def __init__(self, typemap=None, rangemap=None, defaultmap=None, voidmap=None):
93         self._typemap = {}
94         self._rangemap   = {} # possible values
95         self._defaultmap = {} # defaults values
96         self._voidmap    = {}    # None values are allowed
97         
98         if typemap is not None:
99             self._typemap.update(typemap)
100         if rangemap is not None:
101             self._rangemap.update(rangemap)
102         if voidmap is not None:
103             self._voidmap.update(voidmap)
104
105         # Default initialization (if any)
106         if defaultmap is not None:
107             self._defaultmap.update(defaultmap)
108             for name in self._defaultmap:
109                 self.__setattr__(name,self._defaultmap[name])
110
111     ## %A None argument means that no entry is created in the associated maps.
112     def addAttribute(self, name, a_type=None, a_range=None, default=None, void=None):
113         """
114         A None argument means that no entry is created in the associated maps.
115         """
116         self._typemap[name] = a_type
117
118         if a_range is not None:
119             self._rangemap[name] = a_range
120
121         if void is not None:
122             self._voidmap[name] = void
123
124         if (not void) and (default is None):
125             return
126         
127         self.__setattr__(name,default)
128
129     def __setattr__(self, name, val):
130         if name in UNCHECKED_ATTRIBUTES:
131             object.__setattr__(self, name, val)
132             return
133
134         #__GBO_DEBUG_
135         if name == "_typemap":
136             print("WARNING WARNING WARNING : changing value of _typemap by ",val)
137
138         if name not in self._typemap:
139             raise DevelException("The class "+str(self.__class__)+" has no attribute "+str(name))
140
141         if val is None:
142             if not self.__isVoidAllowed(name):
143                 raise DevelException("The attribute "+str(name)+" can't be None")
144             else:
145                 # We can stop here and set the value to None
146                 self.__dict__[name] = None
147                 return
148
149         if self.__isNotValidType(name,val):
150             raise DevelException("The attribute "+str(name)+" must be an instance of "+str(self._typemap[name]))
151
152         if self.__isNotValidRange(name,val):
153             raise DevelException("The attribute "+str(name)+" must be a value in :"+str(self._rangemap[name]))
154
155         self.__dict__[name] = val
156     
157     def __getattribute__(self, name):
158         if name in UNCHECKED_ATTRIBUTES:
159             return object.__getattribute__(self, name)
160
161         if name in DataModeler.__dict__:
162             return object.__getattribute__(self, name)
163
164         if name not in self._typemap:
165             raise DevelException("The class  has no attribute "+str(name))
166         # The attribute coulb be requested while it has not been created yet (for
167         # example if we did't call the setter before).
168         if name not in self.__dict__.keys():
169             return None
170
171         return object.__getattribute__(self, name)
172
173     def __isNotValidType(self, name, val):
174         isNotValid = (
175             ( self._typemap[name] is not None) and
176             ( not isinstance(val,self._typemap[name]) ) )
177
178         return isNotValid
179
180     def __isNotValidRange(self, name, val):
181         isNotValid = (
182             ( self._rangemap is not None) and
183             ( name in self._rangemap ) and
184             ( self._rangemap[name] is not None ) and
185             ( val not in self._rangemap[name] ) )
186
187         return isNotValid
188
189     def __isVoidAllowed(self,name):
190         isVoidAllowed = (
191             ( self._voidmap is not None) and
192             ( name in self._voidmap ) and
193             ( self._voidmap[name] is True ) )
194             
195         return isVoidAllowed
196
197     def log(self):
198         print("DATAMODELER ["+str(self.__class__)+"]: self._typemap.keys() = "+str(list(self._typemap.keys())))
199
200
201
202
203 #
204 # ==============================================================================
205 # Basic use cases and unit tests
206 # ==============================================================================
207 #
208 def TEST_usecase():
209     typemap={}
210     typemap["stringdata"] = TypeString
211     typemap["integerdata"] = TypeInteger
212     typemap["anydata"] = None # can be anything
213
214     data = DataModeler(typemap)
215
216     sdata = "toto"
217     idata = 3
218     data.stringdata = sdata
219     data.integerdata = idata
220
221     data.anydata = 5.3
222     data.anydata = "any value"
223     data.anydata = True
224
225     print(data.integerdata)
226     return True
227
228 def TEST_addAttribute():
229     typemap={}
230     typemap["stringdata"] = TypeString
231     typemap["integerdata"] = TypeInteger
232     data = DataModeler(typemap)
233     data.stringdata = "a string value"
234
235     ref_value = 1.3
236     data.addAttribute(
237         name    = "myAttr",
238         a_type    = TypeDouble,
239         a_range   = None,
240         default = ref_value,
241         void    = False)
242
243     try:
244         if data.myAttr != ref_value:
245             return False
246         data.myAttr = 5.3
247         #data.myAttr = 5
248     except Exception as e:
249         print(e)
250         return False
251
252     try:
253         data.myAttr = "bad type value"
254         return False
255     except Exception as e:
256         print(e)
257         return True
258
259 def TEST_badAttributeName():
260     map={}
261     map["stringdata"] = TypeString
262     map["integerdata"] = TypeInteger
263
264     data = DataModeler(map)
265
266     # this should raise an exception
267     try:
268         data.myatt = 3
269         return False
270     except Exception as e:
271         print("OK : "+str(e))
272         return True
273
274 def TEST_badAttributeType():
275     map={}
276     map["stringdata"] = TypeString
277     map["integerdata"] = TypeInteger
278
279     data = DataModeler(map)
280     # this should raise an exception
281     try:
282         data.stringdata = 2
283         return False
284     except Exception as e:
285         print("OK : "+str(e))
286         return True
287
288 def TEST_badAttributeRange():
289     map={}
290     map["stringdata"] = TypeString
291     map["integerdata"] = TypeInteger
292
293     range={}
294     ref_integervalue = 3
295     range["integerdata"] = [1,ref_integervalue,7]
296
297     data = DataModeler(map,range)
298     # this should not raise an exception
299     try:
300         data.integerdata = ref_integervalue
301         data.stringdata = "anything (no restriction has been defined)"
302     except Exception as e:
303         print(e)
304         return False
305
306     # this should raise an exception
307     try:
308         data.integerdata = 9999 # a value not in the range
309         return False
310     except Exception as e:
311         print(e)
312         return True
313
314 def TEST_voidAttributeAllowed():
315     map={}
316     map["stringdata"] = TypeString
317     map["integerdata"] = TypeInteger
318
319     voidmap={}
320     voidmap["stringdata"] = True
321
322     data = DataModeler(typemap=map,voidmap=voidmap)
323     try:
324         # this should not raise an exception
325         data.stringdata = None
326         print(data.stringdata)
327     except Exception as e:
328         print(e)
329         return False
330     
331     try:
332         # this should raise an exception
333         data.integerdata = None
334         return False
335     except Exception as e:
336         print(e)
337         return True
338
339 def TEST_defaultValues():
340     typemap={}
341     typemap["stringdata"] = TypeString
342     typemap["integerdata"] = TypeInteger
343
344     ref_value = "my initial value"
345     defaultmap={}
346     defaultmap["stringdata"] = ref_value
347
348     data = DataModeler(typemap=typemap,defaultmap=defaultmap)
349     print(data.stringdata)
350     if data.stringdata != ref_value:
351         return False
352     else:
353         return True
354
355 if __name__ == "__main__":
356     from .unittester import run
357     run("salome/kernel/datamodeler","TEST_usecase")
358     run("salome/kernel/datamodeler","TEST_addAttribute")
359     run("salome/kernel/datamodeler","TEST_badAttributeName")
360     run("salome/kernel/datamodeler","TEST_badAttributeType")
361     run("salome/kernel/datamodeler","TEST_badAttributeRange")
362     run("salome/kernel/datamodeler","TEST_voidAttributeAllowed")
363     run("salome/kernel/datamodeler","TEST_defaultValues")