Browse Source

Refactoring of datahandlers to simplify from_name() method

Warning, this commit is in a broken state. The em_test.py doesn't works yet
because it uses old relation handler (that doesn't exists anymore). The pickle file in
example is broken too
Yann Weber 8 years ago
parent
commit
e5898b29b6
29 changed files with 498 additions and 534 deletions
  1. 0
    0
      em_test.py
  2. 3
    3
      lodel/editorial_model/components.py
  3. 2
    1
      lodel/leapi/datahandlers/__init__.py
  4. 249
    0
      lodel/leapi/datahandlers/base_classes.py
  5. 0
    36
      lodel/leapi/datahandlers/data_field.py
  6. 0
    1
      lodel/leapi/datahandlers/data_fields/__init__.py
  7. 0
    22
      lodel/leapi/datahandlers/data_fields/bool.py
  8. 0
    17
      lodel/leapi/datahandlers/data_fields/datetime.py
  9. 0
    14
      lodel/leapi/datahandlers/data_fields/file.py
  10. 0
    27
      lodel/leapi/datahandlers/data_fields/format.py
  11. 0
    20
      lodel/leapi/datahandlers/data_fields/integer.py
  12. 0
    34
      lodel/leapi/datahandlers/data_fields/regexvarchar.py
  13. 0
    10
      lodel/leapi/datahandlers/data_fields/text.py
  14. 0
    18
      lodel/leapi/datahandlers/data_fields/uid.py
  15. 0
    24
      lodel/leapi/datahandlers/data_fields/varchar.py
  16. 74
    0
      lodel/leapi/datahandlers/datas.py
  17. 98
    0
      lodel/leapi/datahandlers/datas_base.py
  18. 0
    139
      lodel/leapi/datahandlers/field_data_handler.py
  19. 0
    29
      lodel/leapi/datahandlers/reference.py
  20. 65
    0
      lodel/leapi/datahandlers/references.py
  21. 0
    1
      lodel/leapi/datahandlers/references/__init__.py
  22. 0
    25
      lodel/leapi/datahandlers/references/dict.py
  23. 0
    13
      lodel/leapi/datahandlers/references/link.py
  24. 0
    23
      lodel/leapi/datahandlers/references/list.py
  25. 0
    10
      lodel/leapi/datahandlers/references/multiple.py
  26. 0
    22
      lodel/leapi/datahandlers/references/relation.py
  27. 0
    22
      lodel/leapi/datahandlers/references/set.py
  28. 0
    17
      lodel/leapi/datahandlers/references/single.py
  29. 7
    6
      lodel/leapi/lefactory.py

test_em.py → em_test.py View File


+ 3
- 3
lodel/editorial_model/components.py View File

@@ -6,7 +6,6 @@ import copy
6 6
 import hashlib
7 7
 
8 8
 from lodel.utils.mlstring import MlString
9
-from lodel.leapi.datahandlers.field_data_handler import FieldDataHandler
10 9
 
11 10
 from lodel.editorial_model.exceptions import *
12 11
 
@@ -156,7 +155,7 @@ class EmClass(EmComponent):
156 155
 
157 156
 ## @brief Handles editorial model classes fields
158 157
 class EmField(EmComponent):
159
-    
158
+
160 159
     ## @brief Instanciate a new EmField
161 160
     # @param uid str : uniq identifier
162 161
     # @param display_name MlString|str|dict : field display_name
@@ -165,9 +164,10 @@ class EmField(EmComponent):
165 164
     # @param group EmGroup :
166 165
     # @param **handler_kwargs : data handler arguments
167 166
     def __init__(self, uid, data_handler, display_name = None, help_text = None, group = None, **handler_kwargs):
167
+        from lodel.leapi.datahandlers.base_classes import DataHandler
168 168
         super().__init__(uid, display_name, help_text, group)
169 169
         self.data_handler_name = data_handler
170
-        self.data_handler_cls = FieldDataHandler.from_name(data_handler)
170
+        self.data_handler_cls = DataHandler.from_name(data_handler)
171 171
         #if 'data_handler_kwargs' in handler_kwargs:
172 172
         #    handler_kwargs = handler_kwargs['data_handler_kwargs']
173 173
         self.data_handler_options = handler_kwargs

+ 2
- 1
lodel/leapi/datahandlers/__init__.py View File

@@ -1 +1,2 @@
1
-__author__ = 'roland'
1
+from lodel.leapi.datahandlers.base_classes import DataHandler
2
+DataHandler.load_base_handlers()

+ 249
- 0
lodel/leapi/datahandlers/base_classes.py View File

@@ -0,0 +1,249 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+## @package lodel.leapi.datahandlers.base_classes Define all base/abstract class for data handlers
4
+#
5
+# Contains custom exceptions too
6
+
7
+import copy
8
+import importlib
9
+import inspect
10
+
11
+
12
+class FieldValidationError(Exception):
13
+    pass
14
+
15
+## @brief Base class for all data handlers
16
+class DataHandler(object):
17
+    
18
+    __HANDLERS_MODULES = ('datas_base', 'datas', 'references')
19
+    ## @brief Stores the DataHandler childs classes indexed by name
20
+    __base_handlers = None
21
+    ## @brief Stores custom datahandlers classes indexed by name
22
+    # @todo do it ! (like plugins, register handlers... blablabla)
23
+    __custom_handlers = dict()
24
+
25
+    help_text = 'Generic Field Data Handler'
26
+
27
+    ## @brief List fields that will be exposed to the construct_data_method
28
+    _construct_datas_deps = []
29
+
30
+    ## @brief constructor
31
+    # @param internal False | str : define whether or not a field is internal
32
+    # @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
33
+    #                         designed globally and immutable
34
+    # @param **args
35
+    # @throw NotImplementedError if it is instanciated directly
36
+    def __init__(self, internal=False, immutable=False, primary_key = False, **args):
37
+        if self.__class__ == DataHandler:
38
+            raise NotImplementedError("Abstract class")
39
+        
40
+        self.primary_key = primary_key
41
+        self.internal = internal  # Check this value ?
42
+        self.immutable = bool(immutable)
43
+
44
+        for argname, argval in args.items():
45
+            setattr(self, argname, argval)
46
+
47
+    ## Fieldtype name
48
+    @staticmethod
49
+    def name(cls):
50
+        return cls.__module__.split('.')[-1]
51
+
52
+    def is_primary_key(self):
53
+        return self.primary_key
54
+
55
+    ## @brief checks if a fieldtype is internal
56
+    # @return bool
57
+    def is_internal(self):
58
+        return self.internal is not False
59
+
60
+    ## @brief calls the data_field defined _check_data_value() method
61
+    # @return tuple (value, error|None)
62
+    def check_data_value(self, value):
63
+        return self._check_data_value(value)
64
+
65
+    def _check_data_value(self, value):
66
+        return value, None
67
+
68
+    ## @brief checks if this class can override the given data handler
69
+    # @param data_handler DataHandler
70
+    # @return bool
71
+    def can_override(self, data_handler):
72
+        if data_handler.__class__.base_type != self.__class__.base_type:
73
+            return False
74
+        return True
75
+
76
+    ## @brief Build field value
77
+    # @param emcomponent EmComponent : An EmComponent child class instance
78
+    # @param fname str : The field name
79
+    # @param datas dict : dict storing fields values (from the component)
80
+    # @param cur_value : the value from the current field (identified by fieldname)
81
+    # @return the value
82
+    # @throw RunTimeError if data construction fails
83
+    def construct_data(self, emcomponent, fname, datas, cur_value):
84
+        emcomponent_fields = emcomponent.fields()
85
+        fname_data_handler = None
86
+        if fname in emcomponent_fields:
87
+            fname_data_handler = DataHandler.from_name(emcomponent_fields[fname])
88
+
89
+        if fname in datas.keys():
90
+            return cur_value
91
+        elif fname_data_handler is not None and hasattr(fname_data_handler, 'default'):
92
+                return fname_data_handler.default
93
+        elif fname_data_handler is not None and fname_data_handler.nullable:
94
+                return None
95
+
96
+        return RuntimeError("Unable to construct data for field %s", fname)
97
+
98
+    ## @brief Check datas consistency
99
+    # @param emcomponent EmComponent : An EmComponent child class instance
100
+    # @param fname : the field name
101
+    # @param datas dict : dict storing fields values
102
+    # @return an Exception instance if fails else True
103
+    # @todo A implémenter
104
+    def check_data_consistency(self, emcomponent, fname, datas):
105
+        return True
106
+
107
+    ## @brief This method is use by plugins to register new data handlers
108
+    @classmethod
109
+    def register_new_handler(cls, name, data_handler):
110
+        if not inspect.isclass(data_handler):
111
+            raise ValueError("A class was expected but %s given" % type(data_handler))
112
+        if not issubclass(data_handler, DataHandler):
113
+            raise ValueError("A data handler HAS TO be a child class of DataHandler")
114
+        cls.__custom_handlers[name] = data_handler
115
+
116
+    @classmethod
117
+    def load_base_handlers(cls):
118
+        if cls.__base_handlers is None:
119
+            cls.__base_handlers = dict()
120
+            for module_name in cls.__HANDLERS_MODULES:
121
+                module = importlib.import_module('lodel.leapi.datahandlers.%s' % module_name)
122
+                for name, obj in inspect.getmembers(module):
123
+                    if inspect.isclass(obj):
124
+                        print("Data handler found : %s in %s" % (name, module_name))
125
+                        cls.__base_handlers[name.lower()] = obj
126
+        return copy.copy(cls.__base_handlers)
127
+
128
+    ## @brief given a field type name, returns the associated python class
129
+    # @param fieldtype_name str : A field type name (not case sensitive)
130
+    # @return DataField child class
131
+    # @todo implements custom handlers fetch
132
+    # @note To access custom data handlers it can be cool to preffix the handler name by plugin name for example ? (to ensure name unicity)
133
+    @classmethod
134
+    def from_name(cls, name):
135
+        name = name.lower()
136
+        if name not in cls.__base_handlers:
137
+            raise NameError("No data handlers named '%s'" % (name,))
138
+        return cls.__base_handlers[name]
139
+ 
140
+    ## @brief Return the module name to import in order to use the datahandler
141
+    # @param data_handler_name str : Data handler name
142
+    # @return a str
143
+    @classmethod
144
+    def module_name(cls, name):
145
+        name = name.lower()
146
+        handler_class = cls.from_name(name)
147
+        return '{module_name}.{class_name}'.format(
148
+                                                    module_name = handler_class.__module__,
149
+                                                    class_name = handler_class.__name__
150
+        )
151
+            
152
+    ## @brief __hash__ implementation for fieldtypes
153
+    def __hash__(self):
154
+        hash_dats = [self.__class__.__module__]
155
+        for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
156
+            hash_dats.append((kdic, getattr(self, kdic)))
157
+        return hash(tuple(hash_dats))
158
+
159
+## @brief Base class for datas data handler (by opposition with references)
160
+class DataField(DataHandler):
161
+
162
+    ## @brief Instanciates a new fieldtype
163
+    # @param nullable bool : is None allowed as value ?
164
+    # @param uniq bool : Indicates if a field should handle a uniq value
165
+    # @param primary bool : If true the field is a primary key
166
+    # @param internal str|False: if False, that field is not internal. Other values cans be "autosql" or "internal"
167
+    # @param **kwargs : Other arguments
168
+    # @throw NotImplementedError if called from bad class
169
+    def __init__(self, internal=False, nullable=True, uniq=False, primary=False, **kwargs):
170
+        if self.__class__ == DataField:
171
+            raise NotImplementedError("Abstract class")
172
+
173
+        super().__init__(internal, **kwargs)
174
+
175
+        self.nullable = nullable
176
+        self.uniq = uniq
177
+        self.primary = primary
178
+        if 'defaults' in kwargs:
179
+            self.default, error = self.check_data_value(kwargs['default'])
180
+            if error:
181
+                raise error
182
+            del(args['default'])
183
+
184
+    def check_data_value(self, value):
185
+        if value is None:
186
+            if not self.nullable:
187
+                return None, TypeError("'None' value but field is not nullable")
188
+
189
+            return None, None
190
+        return super().check_data_value(value)
191
+
192
+
193
+## @brief Abstract class for all references
194
+#
195
+# References are fields that stores a reference to another
196
+# editorial object
197
+class Reference(DataHandler):
198
+
199
+    ## @brief Instanciation
200
+    # @param allowed_classes list | None : list of allowed em classes if None no restriction
201
+    # @param internal bool : if False, the field is not internal
202
+    # @param **kwargs : other arguments
203
+    def __init__(self, allowed_classes = None, internal=False, **kwargs):
204
+        self.__allowed_classes = None if allowed_classes is None else set(allowed_classes)
205
+        super().__init__(internal=internal, **kwargs)
206
+
207
+    ## @brief Check value
208
+    # @param value *
209
+    # @return tuple(value, exception)
210
+    # @todo implement the check when we have LeObject to check value
211
+    def _check_data_value(self, value):
212
+        return value, None
213
+        if isinstance(value, lodel.editorial_model.components.EmClass):
214
+            value = [value]
215
+        for elt in value:
216
+            if not issubclass(elt.__class__, EmClass):
217
+                return None, FieldValidationError("Some elements of this references are not EmClass instances")
218
+            if self.__allowed_classes is not None:
219
+                if not isinstance(elt, self.__allowed_classes):
220
+                    return None, FieldValidationError("Some element of this references are not valids (don't fit with allowed_classes")
221
+        return value
222
+
223
+
224
+## @brief This class represent a data_handler for single reference to another object
225
+#
226
+# The fields using this data handlers are like "foreign key" on another object
227
+class SingleRef(Reference):
228
+    
229
+    def __init__(self, allowed_classes = None, **kwargs):
230
+        super().__init__(allowed_classes = allowed_classes)
231
+ 
232
+    def _check_data_value(self, value):
233
+        val, expt = super()._check_data_value(value)
234
+        if not isinstance(expt, Exception):
235
+            if len(val) > 1:
236
+               return None, FieldValidationError("Only single values are allowed for SingleRef fields")
237
+        return val, expt
238
+
239
+
240
+## @brief This class represent a data_handler for multiple references to another object
241
+#
242
+# The fields using this data handlers are like SingleRef but can store multiple references in one field
243
+# @note SQL implementation could be tricky
244
+class MultipleRef(Reference):
245
+    
246
+    def __init__(self, allowed_classes = None, **kwargs):
247
+        super().__init__(allowed_classes = allowed_classes)
248
+ 
249
+

+ 0
- 36
lodel/leapi/datahandlers/data_field.py View File

@@ -1,36 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from .field_data_handler import FieldDataHandler
3
-
4
-
5
-class DataField(FieldDataHandler):
6
-
7
-    ## @brief Instanciates a new fieldtype
8
-    # @param nullable bool : is None allowed as value ?
9
-    # @param uniq bool : Indicates if a field should handle a uniq value
10
-    # @param primary bool : If true the field is a primary key
11
-    # @param internal str|False: if False, that field is not internal. Other values cans be "autosql" or "internal"
12
-    # @param **kwargs : Other arguments
13
-    # @throw NotImplementedError if called from bad class
14
-    def __init__(self, internal=False, nullable=True, uniq=False, primary=False, **kwargs):
15
-        if self.__class__ == DataField:
16
-            raise NotImplementedError("Abstract class")
17
-
18
-        super().__init__(internal, **kwargs)
19
-
20
-        self.nullable = nullable
21
-        self.uniq = uniq
22
-        self.primary = primary
23
-        if 'defaults' in kwargs:
24
-            self.default, error = self.check_data_value(kwargs['default'])
25
-            if error:
26
-                raise error
27
-            del(args['default'])
28
-
29
-    def check_data_value(self, value):
30
-        if value is None:
31
-            if not self.nullable:
32
-                return None, TypeError("'None' value but field is not nullable")
33
-
34
-            return None, None
35
-        return super().check_data_value(value)
36
-

+ 0
- 1
lodel/leapi/datahandlers/data_fields/__init__.py View File

@@ -1 +0,0 @@
1
-

+ 0
- 22
lodel/leapi/datahandlers/data_fields/bool.py View File

@@ -1,22 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from ..data_field import DataField
3
-
4
-
5
-class DataHandler(DataField):
6
-
7
-    help = 'A basic boolean field'
8
-    base_type = 'bool'
9
-
10
-    ## @brief A boolean field
11
-    def __init__(self, **kwargs):
12
-        if 'check_data_value' not in kwargs:
13
-            kwargs['check_data_value'] = self.check_value
14
-        super().__init__(ftype='bool', **kwargs)
15
-
16
-    def _check_data_value(self, value):
17
-        error = None
18
-        try:
19
-            value = bool(value)
20
-        except(ValueError, TypeError):
21
-            error = TypeError("The value '%s' is not, and will never, be a boolean" % value)
22
-        return value, error

+ 0
- 17
lodel/leapi/datahandlers/data_fields/datetime.py View File

@@ -1,17 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from ..data_field import DataField
3
-
4
-
5
-class DataHandler(DataField):
6
-
7
-    help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
8
-    base_type = 'datetime'
9
-
10
-    ## @brief A datetime field
11
-    # @param now_on_update bool : If true, the date is set to NOW on update
12
-    # @param now_on_create bool : If true, the date is set to NEW on creation
13
-    # @param **kwargs
14
-    def __init__(self, now_on_update=False, now_on_create=False, **kwargs):
15
-        self.now_on_update = now_on_update
16
-        self.now_on_create = now_on_create
17
-        super().__init__(**kwargs)

+ 0
- 14
lodel/leapi/datahandlers/data_fields/file.py View File

@@ -1,14 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from ..data_field import DataField
3
-
4
-
5
-class DataHandler(DataField):
6
-
7
-    base_type = 'file'
8
-
9
-    ## @brief a file field
10
-    # @param upload_path str : None by default
11
-    # @param **kwargs
12
-    def __init__(self, upload_path=None, **kwargs):
13
-        self.upload_path = upload_path
14
-        super().__init__(**kwargs)

+ 0
- 27
lodel/leapi/datahandlers/data_fields/format.py View File

@@ -1,27 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-import warnings
3
-from .varchar import DataHandler as VarcharDataHandler
4
-
5
-
6
-class DataHandler(VarcharDataHandler):
7
-
8
-    help = 'Automatic string field, designed to use the str % operator to build its content'
9
-    base_type = 'char'
10
-
11
-    ## @brief Build its content with a field list and a format string
12
-    # @param format_string str
13
-    # @param max_length int : the maximum length of the handled value
14
-    # @param field_list list : List of field to use
15
-    # @param **kwargs
16
-    def __init__(self, format_string, field_list, max_length, **kwargs):
17
-        self._field_list = field_list
18
-        self._format_string = format_string
19
-        super().__init__(internal='automatic', max_length=max_length)
20
-
21
-
22
-    def can_override(self, data_handler):
23
-        if not super().can_override(data_handler):
24
-            return False
25
-        if data_handler.max_length != self.max_length:
26
-            return False
27
-        return True

+ 0
- 20
lodel/leapi/datahandlers/data_fields/integer.py View File

@@ -1,20 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from ..data_field import FieldDataHandler
3
-
4
-
5
-class DataHandler(FieldDataHandler):
6
-
7
-    help = 'Basic integer field'
8
-    base_type = 'int'
9
-
10
-    def __init__(self, **kwargs):
11
-        super().__init__(base_type='int', **kwargs)
12
-
13
-    def _check_data_value(self, value):
14
-        error = None
15
-        try:
16
-            value = int(value)
17
-        except(ValueError, TypeError):
18
-            error = TypeError("The value '%s' is not, and will never, be an integer" % value)
19
-        return value, error
20
-

+ 0
- 34
lodel/leapi/datahandlers/data_fields/regexvarchar.py View File

@@ -1,34 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-import re
3
-from .varchar import DataHandler as VarcharDataHandler
4
-
5
-
6
-class DataHandler(VarcharDataHandler):
7
-
8
-    help = 'String field validated with a regex. Takes two options : max_length and regex'
9
-    base_type = 'char'
10
-
11
-    ## @brief A string field validated by a regex
12
-    # @param regex str : a regex string (passed as argument to re.compile())
13
-    # @param max_length int : the max length for this field (default : 10)
14
-    # @param **kwargs
15
-    def __init__(self, regex='', max_length=10, **kwargs):
16
-        self.regex = regex
17
-        self.compiled_re = re.compile(regex)  # trigger an error if invalid regex
18
-
19
-        super(self.__class__, self).__init__(max_length=max_length, **kwargs)
20
-
21
-    def _check_data_value(self, value):
22
-        error = None
23
-        if not self.compiled_re.match(value):
24
-            value = ''
25
-            error = TypeError('"%s" doesn\'t match the regex "%s"' % (value, self.regex))
26
-        return value, error
27
-
28
-    def can_override(self, data_handler):
29
-        if not super().can_override(data_handler):
30
-            return False
31
-
32
-        if data_handler.max_length != self.max_length:
33
-            return False
34
-        return True

+ 0
- 10
lodel/leapi/datahandlers/data_fields/text.py View File

@@ -1,10 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from ..data_field import DataField
3
-
4
-
5
-class DataHandler(DataField):
6
-    help = 'A text field (big string)'
7
-    base_type = 'text'
8
-
9
-    def __init__(self, **kwargs):
10
-        super(self.__class__, self).__init__(ftype='text', **kwargs)

+ 0
- 18
lodel/leapi/datahandlers/data_fields/uid.py View File

@@ -1,18 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from .integer import DataHandler as IntegerDataHandler
3
-
4
-
5
-class DataHandler(IntegerDataHandler):
6
-
7
-    help = 'Fieldtype designed to handle editorial model UID'
8
-    base_type = 'int'
9
-
10
-    ## @brief A uid field
11
-    # @param **kwargs
12
-    def __init__(self, is_id_class, **kwargs):
13
-        self._is_id_class = is_id_class
14
-        kwargs['internal'] = 'automatic'
15
-        super(self.__class__, self).__init__(is_id_class=is_id_class, **kwargs)
16
-
17
-    def _check_data_value(self, value):
18
-        return value, None

+ 0
- 24
lodel/leapi/datahandlers/data_fields/varchar.py View File

@@ -1,24 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from ..data_field import DataField
3
-
4
-
5
-class DataHandler(DataField):
6
-
7
-    help = 'Basic string (varchar) field. Default size is 64 characters'
8
-    base_type = 'char'
9
-
10
-    ## @brief A string field
11
-    # @brief max_length int: The maximum length of this field
12
-    def __init__(self, max_length=64, **kwargs):
13
-        self.max_length = int(max_length)
14
-        super().__init__(**kwargs)
15
-
16
-    ## @brief checks if this class can override the given data handler
17
-    # @param data_handler DataHandler
18
-    # @return bool
19
-    def can_override(self, data_handler):
20
-        if not super().can_override(data_handler):
21
-            return False
22
-        if data_handler.max_length != self.max_length:
23
-            return False
24
-        return True

+ 74
- 0
lodel/leapi/datahandlers/datas.py View File

@@ -0,0 +1,74 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+from lodel.leapi.datahandlers.datas_base import *
4
+
5
+## @brief Data field designed to handle formated strings
6
+class FormatString(Varchar):
7
+
8
+    help = 'Automatic string field, designed to use the str % operator to build its content'
9
+    base_type = 'char'
10
+
11
+    ## @brief Build its content with a field list and a format string
12
+    # @param format_string str
13
+    # @param max_length int : the maximum length of the handled value
14
+    # @param field_list list : List of field to use
15
+    # @param **kwargs
16
+    def __init__(self, format_string, field_list, max_length, **kwargs):
17
+        self._field_list = field_list
18
+        self._format_string = format_string
19
+        super().__init__(internal='automatic', max_length=max_length)
20
+
21
+
22
+    def can_override(self, data_handler):
23
+        if not super().can_override(data_handler):
24
+            return False
25
+        if data_handler.max_length != self.max_length:
26
+            return False
27
+        return True
28
+
29
+## @brief Varchar validated by a regex
30
+class Regex(Varchar):
31
+
32
+    help = 'String field validated with a regex. Takes two options : max_length and regex'
33
+    base_type = 'char'
34
+
35
+    ## @brief A string field validated by a regex
36
+    # @param regex str : a regex string (passed as argument to re.compile())
37
+    # @param max_length int : the max length for this field (default : 10)
38
+    # @param **kwargs
39
+    def __init__(self, regex='', max_length=10, **kwargs):
40
+        self.regex = regex
41
+        self.compiled_re = re.compile(regex)  # trigger an error if invalid regex
42
+
43
+        super(self.__class__, self).__init__(max_length=max_length, **kwargs)
44
+
45
+    def _check_data_value(self, value):
46
+        error = None
47
+        if not self.compiled_re.match(value):
48
+            value = ''
49
+            error = TypeError('"%s" doesn\'t match the regex "%s"' % (value, self.regex))
50
+        return value, error
51
+
52
+    def can_override(self, data_handler):
53
+        if not super().can_override(data_handler):
54
+            return False
55
+
56
+        if data_handler.max_length != self.max_length:
57
+            return False
58
+        return True
59
+
60
+## @brief Handles uniq ID
61
+class UniqID(Integer):
62
+
63
+    help = 'Fieldtype designed to handle editorial model UID'
64
+    base_type = 'int'
65
+
66
+    ## @brief A uid field
67
+    # @param **kwargs
68
+    def __init__(self, is_id_class, **kwargs):
69
+        self._is_id_class = is_id_class
70
+        kwargs['internal'] = 'automatic'
71
+        super(self.__class__, self).__init__(is_id_class=is_id_class, **kwargs)
72
+
73
+    def _check_data_value(self, value):
74
+        return value, None

+ 98
- 0
lodel/leapi/datahandlers/datas_base.py View File

@@ -0,0 +1,98 @@
1
+#-*- coding: utf-8 -*-
2
+import warnings
3
+
4
+from lodel.leapi.datahandlers.base_classes import DataField
5
+
6
+## @brief Data field designed to handle boolean values
7
+class Boolean(DataField):
8
+
9
+    help = 'A basic boolean field'
10
+    base_type = 'bool'
11
+
12
+    ## @brief A boolean field
13
+    def __init__(self, **kwargs):
14
+        if 'check_data_value' not in kwargs:
15
+            kwargs['check_data_value'] = self.check_value
16
+        super().__init__(ftype='bool', **kwargs)
17
+
18
+    def _check_data_value(self, value):
19
+        error = None
20
+        try:
21
+            value = bool(value)
22
+        except(ValueError, TypeError):
23
+            error = TypeError("The value '%s' is not, and will never, be a boolean" % value)
24
+        return value, error
25
+
26
+## @brief Data field designed to handle integer values
27
+class Integer(DataField):
28
+
29
+    help = 'Basic integer field'
30
+    base_type = 'int'
31
+
32
+    def __init__(self, **kwargs):
33
+        super().__init__(base_type='int', **kwargs)
34
+
35
+    def _check_data_value(self, value):
36
+        error = None
37
+        try:
38
+            value = int(value)
39
+        except(ValueError, TypeError):
40
+            error = TypeError("The value '%s' is not, and will never, be an integer" % value)
41
+        return value, error
42
+
43
+## @brief Data field designed to handle string
44
+class Varchar(DataField):
45
+
46
+    help = 'Basic string (varchar) field. Default size is 64 characters'
47
+    base_type = 'char'
48
+
49
+    ## @brief A string field
50
+    # @brief max_length int: The maximum length of this field
51
+    def __init__(self, max_length=64, **kwargs):
52
+        self.max_length = int(max_length)
53
+        super().__init__(**kwargs)
54
+
55
+    ## @brief checks if this class can override the given data handler
56
+    # @param data_handler DataHandler
57
+    # @return bool
58
+    def can_override(self, data_handler):
59
+        if not super().can_override(data_handler):
60
+            return False
61
+        if data_handler.max_length != self.max_length:
62
+            return False
63
+        return True
64
+
65
+## @brief Data field designed to handle date & time 
66
+class DateTime(DataField):
67
+
68
+    help = 'A datetime field. Take two boolean options now_on_update and now_on_create'
69
+    base_type = 'datetime'
70
+
71
+    ## @brief A datetime field
72
+    # @param now_on_update bool : If true, the date is set to NOW on update
73
+    # @param now_on_create bool : If true, the date is set to NEW on creation
74
+    # @param **kwargs
75
+    def __init__(self, now_on_update=False, now_on_create=False, **kwargs):
76
+        self.now_on_update = now_on_update
77
+        self.now_on_create = now_on_create
78
+        super().__init__(**kwargs)
79
+
80
+## @brief Data field designed to handle long string
81
+class Text(DataField):
82
+    help = 'A text field (big string)'
83
+    base_type = 'text'
84
+
85
+    def __init__(self, **kwargs):
86
+        super(self.__class__, self).__init__(ftype='text', **kwargs)
87
+
88
+## @brief Data field designed to handle Files
89
+class File(DataField):
90
+
91
+    base_type = 'file'
92
+
93
+    ## @brief a file field
94
+    # @param upload_path str : None by default
95
+    # @param **kwargs
96
+    def __init__(self, upload_path=None, **kwargs):
97
+        self.upload_path = upload_path
98
+        super().__init__(**kwargs)

+ 0
- 139
lodel/leapi/datahandlers/field_data_handler.py View File

@@ -1,139 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-import importlib
3
-
4
-class FieldValidationError(Exception):
5
-    pass
6
-
7
-class FieldDataHandler(object):
8
-
9
-    help_text = 'Generic Field Data Handler'
10
-
11
-    ## @brief List fields that will be exposed to the construct_data_method
12
-    _construct_datas_deps = []
13
-
14
-    ## @brief constructor
15
-    # @param internal False | str : define whether or not a field is internal
16
-    # @param immutable bool : indicates if the fieldtype has to be defined in child classes of LeObject or if it is
17
-    #                         designed globally and immutable
18
-    # @param **args
19
-    # @throw NotImplementedError if it is instanciated directly
20
-    def __init__(self, internal=False, immutable=False, primary_key = False, **args):
21
-        if self.__class__ == FieldDataHandler:
22
-            raise NotImplementedError("Abstract class")
23
-        
24
-        self.primary_key = primary_key
25
-        self.internal = internal  # Check this value ?
26
-        self.immutable = bool(immutable)
27
-
28
-        for argname, argval in args.items():
29
-            setattr(self, argname, argval)
30
-
31
-
32
-    ## Fieldtype name
33
-    @staticmethod
34
-    def name(cls):
35
-        return cls.__module__.split('.')[-1]
36
-
37
-    def is_primary_key(self):
38
-        return self.primary_key
39
-
40
-    ## @brief checks if a fieldtype is internal
41
-    # @return bool
42
-    def is_internal(self):
43
-        return self.internal is not False
44
-
45
-    ## @brief calls the data_field defined _check_data_value() method
46
-    # @return tuple (value, error|None)
47
-    def check_data_value(self, value):
48
-        return self._check_data_value(value)
49
-
50
-    def _check_data_value(self, value):
51
-        return value, None
52
-
53
-    ## @brief checks if this class can override the given data handler
54
-    # @param data_handler DataHandler
55
-    # @return bool
56
-    def can_override(self, data_handler):
57
-        if data_handler.__class__.base_type != self.__class__.base_type:
58
-            return False
59
-        return True
60
-
61
-    ## @brief Build field value
62
-    # @param emcomponent EmComponent : An EmComponent child class instance
63
-    # @param fname str : The field name
64
-    # @param datas dict : dict storing fields values (from the component)
65
-    # @param cur_value : the value from the current field (identified by fieldname)
66
-    # @return the value
67
-    # @throw RunTimeError if data construction fails
68
-    def construct_data(self, emcomponent, fname, datas, cur_value):
69
-        emcomponent_fields = emcomponent.fields()
70
-        fname_data_handler = None
71
-        if fname in emcomponent_fields:
72
-            fname_data_handler = FieldDataHandler.from_name(emcomponent_fields[fname])
73
-
74
-        if fname in datas.keys():
75
-            return cur_value
76
-        elif fname_data_handler is not None and hasattr(fname_data_handler, 'default'):
77
-                return fname_data_handler.default
78
-        elif fname_data_handler is not None and fname_data_handler.nullable:
79
-                return None
80
-
81
-        return RuntimeError("Unable to construct data for field %s", fname)
82
-
83
-    ## @brief Check datas consistency
84
-    # @param emcomponent EmComponent : An EmComponent child class instance
85
-    # @param fname : the field name
86
-    # @param datas dict : dict storing fields values
87
-    # @return an Exception instance if fails else True
88
-    # @todo A implémenter
89
-    def check_data_consistency(self, emcomponent, fname, datas):
90
-        return True
91
-
92
-    ## @brief given a field type name, returns the associated python class
93
-    # @param fieldtype_name str : A field type name
94
-    # @return DataField child class
95
-    @staticmethod
96
-    def from_name(data_handler_name):
97
-        data_handler_name = data_handler_name.lower()
98
-        mod = None
99
-        for mname in FieldDataHandler.modules_name(data_handler_name):
100
-            try:
101
-                mod = importlib.import_module(mname)
102
-            except ImportError:
103
-                pass
104
-        if mod is None:
105
-            raise NameError("Unknown data_handler name : '%s'" % data_handler_name)
106
-        return mod.DataHandler
107
-    
108
-    ## @brief Return the module name to import in order to use the datahandler
109
-    # @param data_handler_name str : Data handler name
110
-    # @return a str
111
-    @staticmethod
112
-    def module_name(data_handler_name):
113
-        data_handler_name = data_handler_name.lower()
114
-        for mname in FieldDataHandler.modules_name(data_handler_name):
115
-            try:
116
-                mod = importlib.import_module(mname)
117
-                module_name = mname
118
-            except ImportError:
119
-                pass
120
-        if mod is None:
121
-            raise NameError("Unknown data_handler name : '%s'" % data_handler_name)
122
-        return module_name
123
-
124
-    ## @brief get a module name given a fieldtype name
125
-    # @param fieldtype_name str : a field type name
126
-    # @return a string representing a python module name
127
-    @staticmethod
128
-    def modules_name(fieldtype_name):
129
-        return (
130
-                'lodel.leapi.datahandlers.data_fields.%s' % fieldtype_name,
131
-                'lodel.leapi.datahandlers.references.%s' % fieldtype_name
132
-        )
133
-
134
-    ## @brief __hash__ implementation for fieldtypes
135
-    def __hash__(self):
136
-        hash_dats = [self.__class__.__module__]
137
-        for kdic in sorted([k for k in self.__dict__.keys() if not k.startswith('_')]):
138
-            hash_dats.append((kdic, getattr(self, kdic)))
139
-        return hash(tuple(hash_dats))

+ 0
- 29
lodel/leapi/datahandlers/reference.py View File

@@ -1,29 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.field_data_handler import FieldDataHandler, FieldValidationError
3
-from lodel.editorial_model.components import EmClass
4
-
5
-
6
-class Reference(FieldDataHandler):
7
-
8
-    ## @brief Instanciation
9
-    # @param allowed_classes list | None : list of allowed em classes if None no restriction
10
-    # @param internal bool : if False, the field is not internal
11
-    # @param **kwargs : other arguments
12
-    def __init__(self, allowed_classes = None, internal=False, **kwargs):
13
-        self.__allowed_classes = None if allowed_classes is None else set(allowed_classes)
14
-        super().__init__(internal=internal, **kwargs)
15
-
16
-    ## @brief Check value
17
-    # @param value *
18
-    # @return tuple(value, exception)
19
-    def _check_data_value(self, value):
20
-        if isinstance(value, EmClass):
21
-            value = [value]
22
-        for elt in value:
23
-            if not issubclass(elt.__class__, EmClass):
24
-                return None, FieldValidationError("Some elements of this references are not EmClass instances")
25
-            if self.__allowed_classes is not None:
26
-                if not isinstance(elt, self.__allowed_classes):
27
-                    return None, FieldValidationError("Some element of this references are not valids (don't fit with allowed_classes")
28
-        return value
29
-

+ 65
- 0
lodel/leapi/datahandlers/references.py View File

@@ -0,0 +1,65 @@
1
+# -*- coding: utf-8 -*-
2
+from lodel.leapi.datahandlers.base_classes import Reference, MultipleRef, SingleRef
3
+
4
+class Link(SingleRef): pass
5
+
6
+## @brief Child class of MultipleRef where references are represented in the form of a python list
7
+class List(MultipleRef):
8
+
9
+    ## @brief instanciates a list reference
10
+    # @param allowed_classes list | None : list of allowed em classes if None no restriction
11
+    # @param internal bool
12
+    # @param kwargs
13
+    def __init__(self, allowed_classes=None, internal=False, **kwargs):
14
+        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)
15
+
16
+    ## @brief Check value
17
+    # @param value *
18
+    # @return tuple(value, exception)
19
+    def _check_data_value(self, value):
20
+        val, expt = super()._check_data_value()
21
+        if not isinstance(expt, Exception):
22
+            val = list(val)
23
+        return val, expt
24
+
25
+
26
+## @brief Child class of MultipleRef where references are represented in the form of a python set
27
+class Set(MultipleRef):
28
+
29
+    ## @brief instanciates a set reference
30
+    # @param allowed_classes list | None : list of allowed em classes if None no restriction
31
+    # @param internal bool : if False, the field is not internal
32
+    # @param kwargs : Other named arguments
33
+    def __init__(self, allowed_classes=None, internal=False, **kwargs):
34
+        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)
35
+
36
+    ## @brief Check value
37
+    # @param value *
38
+    # @return tuple(value, exception)
39
+    def _check_data_value(self, value):
40
+        val, expt = super()._check_data_value()
41
+        if not isinstance(expt, Exception):
42
+            val = set(val)
43
+        return val, expt
44
+
45
+
46
+## @brief Child class of MultipleRef where references are represented in the form of a python dict
47
+class Map(MultipleRef):
48
+
49
+    ## @brief instanciates a dict reference
50
+    # @param allowed_classes list | None : list of allowed em classes if None no restriction
51
+    # @param internal bool : if False, the field is not internal
52
+    # @param kwargs : Other named arguments
53
+    def __init__(self, allowed_classes=None, internal=False, **kwargs):
54
+        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)
55
+
56
+    ## @brief Check value
57
+    # @param value *
58
+    # @return tuple(value, exception)
59
+    def _check_data_value(self, value):
60
+        if not isinstance(value, dict):
61
+            return None, FieldValidationError("Values for dict fields should be dict")
62
+        val, expt = super()._check_data_value(value.values())
63
+        return (
64
+                None if isinstance(expt, Exception) else value,
65
+                expt)

+ 0
- 1
lodel/leapi/datahandlers/references/__init__.py View File

@@ -1 +0,0 @@
1
-__author__ = 'roland'

+ 0
- 25
lodel/leapi/datahandlers/references/dict.py View File

@@ -1,25 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.field_data_handler import FieldValidationError
3
-from lodel.leapi.datahandlers.reference import Reference
4
-from lodel.editorial_model.components import EmClass
5
-
6
-
7
-class DataHandler(Reference):
8
-
9
-    ## @brief instanciates a dict reference
10
-    # @param allowed_classes list | None : list of allowed em classes if None no restriction
11
-    # @param internal bool : if False, the field is not internal
12
-    # @param kwargs : Other named arguments
13
-    def __init__(self, allowed_classes=None, internal=False, **kwargs):
14
-        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)
15
-
16
-    ## @brief Check value
17
-    # @param value *
18
-    # @return tuple(value, exception)
19
-    def _check_data_value(self, value):
20
-        if not isinstance(value, dict):
21
-            return None, FieldValidationError("Values for dict fields should be dict")
22
-        val, expt = super()._check_data_value(value.values())
23
-        return (
24
-                None if isinstance(expt, Exception) else value,
25
-                expt)

+ 0
- 13
lodel/leapi/datahandlers/references/link.py View File

@@ -1,13 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.reference import Reference
3
-from lodel.editorial_model.components import EmClass
4
-
5
-
6
-class DataHandler(Reference):
7
-
8
-    ## @brief instanciates a link reference
9
-    # @param allowed_classes list | None : list of allowed em classes if None no restriction
10
-    # @param internal bool : if False, the field is not internal
11
-    # @param kwargs : Other named arguments
12
-    def __init__(self, allowed_classes=None, internal=False, **kwargs):
13
-        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)

+ 0
- 23
lodel/leapi/datahandlers/references/list.py View File

@@ -1,23 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.field_data_handler import FieldValidationError
3
-from lodel.editorial_model.components import EmClass
4
-from lodel.leapi.datahandlers.reference import Reference
5
-
6
-
7
-class DataHandler(Reference):
8
-
9
-    ## @brief instanciates a list reference
10
-    # @param allowed_classes list | None : list of allowed em classes if None no restriction
11
-    # @param internal bool
12
-    # @param kwargs
13
-    def __init__(self, allowed_classes=None, internal=False, **kwargs):
14
-        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)
15
-
16
-    ## @brief Check value
17
-    # @param value *
18
-    # @return tuple(value, exception)
19
-    def _check_data_value(self, value):
20
-        val, expt = super()._check_data_value()
21
-        if not isinstance(expt, Exception):
22
-            val = list(val)
23
-        return val, expt

+ 0
- 10
lodel/leapi/datahandlers/references/multiple.py View File

@@ -1,10 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.reference import Reference
3
-from lodel.editorial_model.components import EmClass
4
-
5
-## @brief This class represent a data_handler for multiple references to another object
6
-class MultipleRef(Reference):
7
-    
8
-    def __init__(self, allowed_classes = None, **kwargs):
9
-        super().__init__(allowed_classes = allowed_classes
10
-    

+ 0
- 22
lodel/leapi/datahandlers/references/relation.py View File

@@ -1,22 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.field_data_handler import FieldDataHandler
3
-
4
-
5
-class DataHandler(FieldDataHandler):
6
-
7
-    ## @brief Instanciates a Relation object
8
-    # @param datahandler FieldDataHandler
9
-    # @param datahandler_args dict
10
-    # @param reference EmField
11
-    # @param kwargs
12
-    def __init__(self, **kwargs):
13
-        # Data Handler
14
-        data_handler = kwargs['data_handler_kwargs']['data_handler']
15
-        data_handler_args = kwargs['data_handler_kwargs']
16
-        data_handler_class = FieldDataHandler.from_name(data_handler)
17
-        self.data_handler = data_handler_class(**data_handler_args)
18
-
19
-        # Reference
20
-        self.backref_ref = kwargs['data_handler_kwargs']['backreference']
21
-
22
-        super().__init__(**kwargs)

+ 0
- 22
lodel/leapi/datahandlers/references/set.py View File

@@ -1,22 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from lodel.editorial_model.components import EmClass
3
-from lodel.leapi.datahandlers.reference import Reference
4
-
5
-
6
-class DataHandler(Reference):
7
-
8
-    ## @brief instanciates a set reference
9
-    # @param allowed_classes list | None : list of allowed em classes if None no restriction
10
-    # @param internal bool : if False, the field is not internal
11
-    # @param kwargs : Other named arguments
12
-    def __init__(self, allowed_classes=None, internal=False, **kwargs):
13
-        super().__init__(allowed_classes=allowed_classes, internal=internal, **kwargs)
14
-
15
-    ## @brief adds a referenced element
16
-    # @param emclass EmClass
17
-    # @return bool
18
-    def add_ref(self, emclass):
19
-        if isinstance(emclass, EmClass):
20
-            self._refs.add(emclass)
21
-            return True
22
-        return False

+ 0
- 17
lodel/leapi/datahandlers/references/single.py View File

@@ -1,17 +0,0 @@
1
-#-*- coding: utf-8 -*-
2
-from lodel.leapi.datahandlers.reference import Reference
3
-from lodel.editorial_model.components import EmClass
4
-
5
-## @brief This class represent a data_handler for single reference to another object
6
-class SingleRef(Reference):
7
-    
8
-    def __init__(self, allowed_classes = None, **kwargs):
9
-        super().__init__(allowed_classes = allowed_classes
10
- 
11
-    def _check_data_value(self, value):
12
-        val, expt = super()._check_data_value(value)
13
-        if not isinstance(expt, Exception):
14
-            if len(val) > 1:
15
-               return None, FieldValidationError("Only single values are allowed for SingleRef fields")
16
-        return val, expt
17
-    

+ 7
- 6
lodel/leapi/lefactory.py View File

@@ -3,14 +3,16 @@
3 3
 import functools
4 4
 from lodel.editorial_model.components import *
5 5
 from lodel.leapi.leobject import LeObject
6
-from lodel.leapi.datahandlers.field_data_handler import FieldDataHandler
6
+from lodel.leapi.datahandlers.base_classes import DataHandler
7 7
 
8 8
 ## @brief Generate python module code from a given model
9 9
 # @param model lodel.editorial_model.model.EditorialModel
10 10
 def dyncode_from_em(model):
11 11
     
12 12
     cls_code, modules, bootstrap_instr = generate_classes(model)
13
-    imports = "from lodel.leapi.leobject import LeObject\n"
13
+    imports = """from lodel.leapi.leobject import LeObject
14
+from lodel.leapi.datahandlers.base_classes import DataField
15
+"""
14 16
     for module in modules:
15 17
         imports += "import %s\n" % module
16 18
 
@@ -49,11 +51,11 @@ def emclass_sorted_by_deps(emclass_list):
49 51
 
50 52
 ## @brief Given an EmField returns the data_handler constructor suitable for dynamic code
51 53
 def data_handler_constructor(emfield):
52
-    dh_module_name = FieldDataHandler.module_name(emfield.data_handler_name)+'.DataHandler'
54
+    #dh_module_name = DataHandler.module_name(emfield.data_handler_name)+'.DataHandler'
55
+    get_handler_class_instr = 'DataField.from_name(%s)' % repr(emfield.data_handler_name)
53 56
     options = []
54 57
 
55
-    #dh_kwargs =  '{' + (', '.join(['%s: %s' % (repr(name), forge_optval(val)) for name, val in emfield.data_handler_options.items()])) + '}'
56
-    return ('%s(**{' % dh_module_name)+(', '.join([repr(name)+': '+forge_optval(val) for name, val in emfield.data_handler_options.items()])) + '})'
58
+    return ('%s(**{' % get_handler_class_instr)+(', '.join([repr(name)+': '+forge_optval(val) for name, val in emfield.data_handler_options.items()])) + '})'
57 59
             
58 60
 
59 61
 def forge_optval(optval):
@@ -87,7 +89,6 @@ def generate_classes(model):
87 89
         parents = list()    # List of parents EmClass
88 90
         # Determine pk
89 91
         for field in em_class.fields():
90
-            imports.append(FieldDataHandler.module_name(field.data_handler_name))
91 92
             if field.data_handler_instance.is_primary_key():
92 93
                 uid.append(field.uid)
93 94
         # Determine parent for inheritance

Loading…
Cancel
Save