Browse Source

EmGroup implementation + fixes + tests

Yann Weber 8 years ago
parent
commit
c85a78ff8a
3 changed files with 195 additions and 6 deletions
  1. 91
    4
      lodel/editorial_model/components.py
  2. 1
    1
      lodel/editorial_model/model.py
  3. 103
    1
      tests/test_model.py

lodel/editorial_model/component.py → lodel/editorial_model/components.py View File

@@ -1,6 +1,8 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3 3
 import itertools
4
+import warnings
5
+import copy
4 6
 
5 7
 from lodel.utils.mlstring import MlString
6 8
 
@@ -47,6 +49,8 @@ class EmClass(EmComponent):
47 49
             for parent in parents:
48 50
                 if not isinstance(parent, EmClass):
49 51
                     raise ValueError("<class EmClass> expected in parents list, but %s found" % type(parent))
52
+        else:
53
+            parents = list()
50 54
         self.parents = parents
51 55
         ## @brief Stores EmFields instances indexed by field uid
52 56
         self.__fields = dict() 
@@ -63,11 +67,11 @@ class EmClass(EmComponent):
63 67
     ## @brief EmField getter
64 68
     # @param uid None | str : If None returns an iterator on EmField instances else return an EmField instance
65 69
     # @param no_parents bool : If True returns only fields defined is this class and not the one defined in parents classes
66
-    # @return An iterator on EmFields instances (if uid is None) else return an EmField instance
70
+    # @return A list on EmFields instances (if uid is None) else return an EmField instance
67 71
     def fields(self, uid = None, no_parents = False):
68 72
         fields = self.__fields if no_parents else self.__all_fields
69 73
         try:
70
-            return iter(fields.values()) if uid is None else fields[uid]
74
+            return list(fields.values()) if uid is None else fields[uid]
71 75
         except KeyError:
72 76
             raise EditorialModelError("No such EmField '%s'" % uid)
73 77
 
@@ -79,12 +83,13 @@ class EmClass(EmComponent):
79 83
         if emfield.uid in self.__fields:
80 84
             raise EditorialModelException("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
81 85
         self.__fields[emfield.uid] = emfield
86
+        emfield._emclass = self
82 87
     
83 88
     ## @brief Create a new EmField and add it to the EmClass
84 89
     # @param uid str : the EmField uniq id
85 90
     # @param **field_kwargs :  EmField constructor parameters ( see @ref EmField.__init__() ) 
86 91
     def new_field(self, uid, **field_kwargs):
87
-        return self.add_field(EmField(uid, **kwargs))
92
+        return self.add_field(EmField(uid, **field_kwargs))
88 93
 
89 94
 
90 95
 ## @brief Handles editorial model classes fields
@@ -100,6 +105,88 @@ class EmField(EmComponent):
100 105
     def __init__(self, uid, data_handler, display_name = None, help_text = None, group = None, **handler_kwargs):
101 106
         super().__init__(uid, display_name, help_text, group)
102 107
         self.data_handler = data_handler
103
-        self.data_handler_options = data_handler_options
108
+        self.data_handler_options = handler_kwargs
109
+        ## @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
110
+        self._emclass = None
104 111
 
112
+
113
+## @brief Handles functionnal group of EmComponents
114
+class EmGroup(object):
115
+        
116
+    ## @brief Create a new EmGroup
117
+    # @note you should NEVER call the constructor yourself. Use Model.add_group instead
118
+    # @param uid str : Uniq identifier
119
+    # @param depends list : A list of EmGroup dependencies
120
+    # @param display_name MlString|str : 
121
+    # @param help_text MlString|str : 
122
+    def __init__(self, uid, depends = None, display_name = None, help_text = None):
123
+        self.uid = uid
124
+        ## @brief Stores the list of groups that depends on this EmGroup indexed by uid
125
+        self.required_by = dict()
126
+        ## @brief Stores the list of dependencies (EmGroup) indexed by uid
127
+        self.require = dict()
128
+        ## @brief Stores the list of EmComponent instances contained in this group
129
+        self.__components = set()
130
+
131
+        self.display_name = None if display_name is None else MlString(display_name)
132
+        self.help_text = None if help_text is None else MlString(help_text)
133
+        if depends is not None:
134
+            for grp in depends:
135
+                if not isinstance(grp, EmGroup):
136
+                    raise ValueError("EmGroup expected in depends argument but %s found" % grp)
137
+                self.add_dependencie(grp)
138
+    
139
+    ## @brief Returns EmGroup dependencie
140
+    # @param recursive bool : if True return all dependencies and their dependencies
141
+    # @return a dict of EmGroup identified by uid
142
+    def dependencies(self, recursive = False):
143
+        res = copy.copy(self.require)
144
+        if not recursive:
145
+            return res
146
+        to_scan = list(res.values())
147
+        while len(to_scan) > 0:
148
+            cur_dep = to_scan.pop()
149
+            for new_dep in cur_dep.require.values():
150
+                if new_dep not in res:
151
+                    to_scan.append(new_dep)
152
+                    res[new_dep.uid] = new_dep
153
+        return res
154
+
155
+    ## @brief Add components in a group
156
+    # @param components list : EmComponent instance list
157
+    def add_components(self, components):
158
+        for component in components:
159
+            if isinstance(component, EmField):
160
+                if component._emclass is None:
161
+                    warnings.warn("Adding an orphan EmField to an EmGroup")
162
+            elif not isinstance(component, EmClass):
163
+                raise EditorialModelError("Expecting components to be a list of EmComponent, but %s found in the list" % type(component))
164
+        self.__components |= set(components)
165
+
166
+    ## @brief Add a dependencie
167
+    # @param em_group EmGroup|iterable : an EmGroup instance or list of instance
168
+    def add_dependencie(self, grp):
169
+        try:
170
+            for group in grp:
171
+                self.add_dependencie(group)
172
+            return
173
+        except TypeError: pass
174
+                
175
+        if grp.uid in self.require:
176
+            return
177
+        if self.__circular_dependencie(grp):
178
+            raise EditorialModelError("Circular dependencie detected, cannot add dependencie")
179
+        self.require[grp.uid] = grp
180
+        grp.required_by[self.uid] = self
181
+    
182
+    ## @brief Search for circular dependencie
183
+    # @return True if circular dep found else False
184
+    def __circular_dependencie(self, new_dep):
185
+        return self.uid in new_dep.dependencies(True)
186
+
187
+    def __str__(self):
188
+        return "<class EmGroup '%s'>" % self.uid
105 189
     
190
+    ## @todo better implementation
191
+    def __repr__(self):
192
+        return self.__str__()

+ 1
- 1
lodel/editorial_model/model.py View File

@@ -2,7 +2,7 @@
2 2
 from lodel.utils.mlstring import MlString
3 3
 
4 4
 from lodel.editorial_model.exceptions import *
5
-from lodel.editorial_model.component import EmClass, EmField
5
+from lodel.editorial_model.components import EmClass, EmField
6 6
 
7 7
 ## @brief Describe an editorial model
8 8
 class EditorialModel(object):

+ 103
- 1
tests/test_model.py View File

@@ -2,8 +2,9 @@
2 2
 
3 3
 import unittest
4 4
 
5
-from lodel.editorial_model.component import EmComponent, EmClass, EmField
5
+from lodel.editorial_model.components import EmComponent, EmClass, EmField, EmGroup
6 6
 from lodel.utils.mlstring import MlString
7
+from lodel.editorial_model.exceptions import *
7 8
 
8 9
 class EmComponentTestCase(unittest.TestCase):
9 10
     
@@ -18,3 +19,104 @@ class EmClassTestCase(unittest.TestCase):
18 19
         self.assertEqual(cls.uid, 'testClass')
19 20
         self.assertEqual(cls.display_name, MlString('test class'))
20 21
         self.assertEqual(cls.help_text, MlString('A test class'))
22
+
23
+    def test_fields(self):
24
+        """ Bad test on add field method (only check uid presence) """
25
+        cls = EmClass('testClass', 'test_class', 'A test class')
26
+        cls.new_field('name', data_handler = None)
27
+        cls.new_field('string', data_handler = None)
28
+        cls.new_field('lodel_id', data_handler = None)
29
+
30
+        fields = cls.fields()
31
+        self.assertEqual(len(fields), 3)
32
+        self.assertEqual(
33
+            set([f.uid for f in fields]),
34
+            set(['name', 'string', 'lodel_id'])
35
+        )
36
+
37
+class EmGroupTestCase(unittest.TestCase):
38
+    
39
+    def test_init(self):
40
+        """ Test EmGroup instanciation """
41
+        grp = EmGroup('testgrp', display_name = "Test group", help_text="No Help")
42
+        self.assertEqual(grp.uid, 'testgrp')
43
+        self.assertEqual(grp.dependencies(), dict())
44
+        self.assertEqual(grp.display_name, MlString("Test group"))
45
+        self.assertEqual(grp.help_text, MlString("No Help"))
46
+        
47
+        grp2 = EmGroup('test')
48
+        self.assertEqual(grp2.uid, 'test')
49
+        self.assertEqual(grp2.display_name, None)
50
+        self.assertEqual(grp2.help_text, None)
51
+
52
+        grp3 = EmGroup('depends', depends = (grp, grp2))
53
+        self.assertEqual(set(grp3.dependencies().values()), set((grp, grp2)))
54
+
55
+    def test_deps(self):
56
+        """ Test dependencies """
57
+        grp1 = EmGroup('grp1')
58
+        grp2 = EmGroup('grp2')
59
+        grp3 = EmGroup('grp3')
60
+        grp4 = EmGroup('grp4')
61
+
62
+        grp2.add_dependencie(grp1)
63
+        grp3.add_dependencie(grp2)
64
+        grp4.add_dependencie(grp2)
65
+        grp4.add_dependencie(grp1)
66
+
67
+        self.assertEqual(set(grp1.dependencies().values()), set())
68
+        self.assertEqual(set(grp2.dependencies().values()), set([grp1]))
69
+        self.assertEqual(set(grp3.dependencies().values()), set([grp2]))
70
+        self.assertEqual(set(grp4.dependencies().values()), set([grp2, grp1]))
71
+
72
+        self.assertEqual(set(grp3.dependencies(True).values()), set([grp2, grp1]))
73
+        self.assertEqual(set(grp4.dependencies(True).values()), set([grp2, grp1]))
74
+
75
+        self.assertEqual(set(grp1.required_by.values()), set([grp2, grp4]))
76
+        self.assertEqual(set(grp2.required_by.values()), set([grp3, grp4]))
77
+        self.assertEqual(set(grp3.required_by.values()), set())
78
+        self.assertEqual(set(grp4.required_by.values()), set())
79
+
80
+        for grp in [grp1, grp2, grp3, grp4]:
81
+            for uid, dep in grp.dependencies(recursive = True).items():
82
+                self.assertEqual(uid, dep.uid)
83
+            for uid, dep in grp.required_by.items():
84
+                self.assertEqual(uid, dep.uid)
85
+    def test_deps_complex(self):
86
+        """ More complex dependencies handling test """
87
+        grps = [ EmGroup('group%d' % i) for i in range(6) ]
88
+        grps[5].add_dependencie( (grps[1], grps[2], grps[4]) )
89
+        grps[4].add_dependencie( (grps[1], grps[3]) )
90
+        grps[3].add_dependencie( (grps[0],) )
91
+        grps[1].add_dependencie( (grps[2], grps[0]) )
92
+        self.assertEqual(
93
+                            set(grps[5].dependencies(True).values()),
94
+                            set( grps[i] for i in range(5))
95
+        )
96
+        self.assertEqual(
97
+                            set(grps[4].dependencies(True).values()),
98
+                            set( grps[i] for i in range(4))
99
+        )
100
+        grps[2].add_dependencie(grps[0])
101
+        self.assertEqual(
102
+                            set(grps[5].dependencies(True).values()),
103
+                            set( grps[i] for i in range(5))
104
+        )
105
+        self.assertEqual(
106
+                            set(grps[4].dependencies(True).values()),
107
+                            set( grps[i] for i in range(4))
108
+        )
109
+        # Inserting circular deps
110
+        with self.assertRaises(EditorialModelError):
111
+            grps[0].add_dependencie(grps[5])
112
+
113
+    def test_circular_dep(self):
114
+        """ Test circular dependencies detection """
115
+        grps = [ EmGroup('group%d' % i) for i in range(10) ]
116
+        for i in range(1,10):
117
+            grps[i].add_dependencie(grps[i-1])
118
+
119
+        for i in range(1,10):
120
+            for j in range(i+1,10):
121
+                with self.assertRaises(EditorialModelError):
122
+                    grps[i].add_dependencie(grps[j])

Loading…
Cancel
Save