Browse Source

Implements deterministic __hash__ methods for objects

Yann Weber 8 years ago
parent
commit
2835624bad

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

3
 import itertools
3
 import itertools
4
 import warnings
4
 import warnings
5
 import copy
5
 import copy
6
+import hashlib
6
 
7
 
7
 from lodel.utils.mlstring import MlString
8
 from lodel.utils.mlstring import MlString
8
 
9
 
29
             return str(self.uid)
30
             return str(self.uid)
30
         return str(self.display_name)
31
         return str(self.display_name)
31
 
32
 
33
+    def __hash__(self):
34
+        m = hashlib.md5()
35
+        for data in (
36
+                        self.uid,
37
+                        str(hash(self.display_name)),
38
+                        str(hash(self.help_text)),
39
+                        str(hash(self.group)),
40
+        ):
41
+            m.update(bytes(data, 'utf-8'))
42
+        return int.from_bytes(m.digest(), byteorder='big')
43
+
44
+
32
 ## @brief Handles editorial model objects classes
45
 ## @brief Handles editorial model objects classes
33
 #
46
 #
34
 # @note The inheritance system allow child classes to overwrite parents EmField. But it's maybe not a good idea
47
 # @note The inheritance system allow child classes to overwrite parents EmField. But it's maybe not a good idea
84
             raise EditorialModelException("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
97
             raise EditorialModelException("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
85
         self.__fields[emfield.uid] = emfield
98
         self.__fields[emfield.uid] = emfield
86
         emfield._emclass = self
99
         emfield._emclass = self
100
+        return emfield
87
     
101
     
88
     ## @brief Create a new EmField and add it to the EmClass
102
     ## @brief Create a new EmField and add it to the EmClass
89
     # @param uid str : the EmField uniq id
103
     # @param uid str : the EmField uniq id
91
     def new_field(self, uid, **field_kwargs):
105
     def new_field(self, uid, **field_kwargs):
92
         return self.add_field(EmField(uid, **field_kwargs))
106
         return self.add_field(EmField(uid, **field_kwargs))
93
 
107
 
108
+    def __hash__(self):
109
+        m = hashlib.md5()
110
+        payload = str(super().__hash__()) + "1" if self.abstract else "0"
111
+        for p in sorted(self.parents):
112
+            payload += str(hash(p))
113
+        m.update(bytes(payload, 'utf-8'))
114
+        return int.from_bytes(m.digest(), byteorder='big')
115
+
94
 
116
 
95
 ## @brief Handles editorial model classes fields
117
 ## @brief Handles editorial model classes fields
96
 class EmField(EmComponent):
118
 class EmField(EmComponent):
109
         ## @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
131
         ## @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
110
         self._emclass = None
132
         self._emclass = None
111
 
133
 
134
+    ## @warning Not complete !
135
+    # @todo Complete the hash when data handlers becomes available
136
+    def __hash__(self):
137
+        return int.from_bytes(hashlib.md5(
138
+                        bytes(
139
+                                "%s%s" % (  super().__hash__(),
140
+                                            hash(self.data_handler)), 
141
+                                'utf-8')
142
+        ).digest(), byteorder='big')
143
+
112
 
144
 
113
 ## @brief Handles functionnal group of EmComponents
145
 ## @brief Handles functionnal group of EmComponents
114
 class EmGroup(object):
146
 class EmGroup(object):
184
     def __circular_dependencie(self, new_dep):
216
     def __circular_dependencie(self, new_dep):
185
         return self.uid in new_dep.dependencies(True)
217
         return self.uid in new_dep.dependencies(True)
186
 
218
 
219
+    ## @brief Fancy string representation of an EmGroup
220
+    # @return a string
187
     def __str__(self):
221
     def __str__(self):
188
-        return "<class EmGroup '%s'>" % self.uid
222
+        if self.display_name is None:
223
+            return self.uid
224
+        else:
225
+            return self.display_name.get()
226
+
227
+    def __hash__(self):
228
+        
229
+        payload = "%s%s%s" % (self.uid, hash(self.display_name), hash(self.help_text))
230
+        for recurs in (False, True):
231
+            deps = self.dependencies(recurs)
232
+            for dep_uid in sorted(deps.keys()):
233
+                payload += str(hash(deps[dep_uid]))
234
+        for req_by_uid in self.required_by:
235
+            payload += req_by_uid
236
+        return int.from_bytes(
237
+                                bytes(payload, 'utf-8'),
238
+                                byteorder = 'big'
239
+        )
189
     
240
     
190
-    ## @todo better implementation
241
+    ## @brief Complete string representation of an EmGroup
242
+    # @return a string
191
     def __repr__(self):
243
     def __repr__(self):
192
-        return self.__str__()
244
+        return "<class EmGroup '%s' depends : [%s]>" % (self.uid, ', '.join([duid for duid in self.dependencies(False)]) )

+ 42
- 9
lodel/editorial_model/model.py View File

1
 #-*- coding:utf-8 -*-
1
 #-*- coding:utf-8 -*-
2
+
3
+import hashlib
4
+
2
 from lodel.utils.mlstring import MlString
5
 from lodel.utils.mlstring import MlString
3
 
6
 
4
 from lodel.editorial_model.exceptions import *
7
 from lodel.editorial_model.exceptions import *
5
-from lodel.editorial_model.components import EmClass, EmField
8
+from lodel.editorial_model.components import EmClass, EmField, EmGroup
6
 
9
 
7
 ## @brief Describe an editorial model
10
 ## @brief Describe an editorial model
8
 class EditorialModel(object):
11
 class EditorialModel(object):
23
     # @return if uid given return an EmClass else return an EmClass iterator
26
     # @return if uid given return an EmClass else return an EmClass iterator
24
     def classes(self, uid = None):
27
     def classes(self, uid = None):
25
         try:
28
         try:
26
-            return __elt_getter(self.__classes)
29
+            return self.__elt_getter(self.__classes, uid)
27
         except KeyError:
30
         except KeyError:
28
             raise EditorialModelException("EmClass not found : '%s'" % uid)
31
             raise EditorialModelException("EmClass not found : '%s'" % uid)
29
 
32
 
32
     # @return if uid given return an EmGroup else return an EmGroup iterator
35
     # @return if uid given return an EmGroup else return an EmGroup iterator
33
     def groups(self, uid = None):
36
     def groups(self, uid = None):
34
         try:
37
         try:
35
-            return __elt_getter(self.__groups)
38
+            return self.__elt_getter(self.__groups, uid)
36
         except KeyError:
39
         except KeyError:
37
             raise EditorialModelException("EmGroup not found : '%s'" % uid)
40
             raise EditorialModelException("EmGroup not found : '%s'" % uid)
38
 
41
 
39
     ## @brief Add a class to the editorial model
42
     ## @brief Add a class to the editorial model
40
     # @param emclass EmClass : the EmClass instance to add
43
     # @param emclass EmClass : the EmClass instance to add
44
+    # @return emclass
41
     def add_class(self, emclass):
45
     def add_class(self, emclass):
42
         if not isinstance(emclass, EmClass):
46
         if not isinstance(emclass, EmClass):
43
             raise ValueError("<class EmClass> expected but got %s " % type(emclass))
47
             raise ValueError("<class EmClass> expected but got %s " % type(emclass))
44
-        if emclass.uid in self.classes:
48
+        if emclass.uid in self.classes():
45
             raise EditorialModelException('Duplicated uid "%s"' % emclass.uid)
49
             raise EditorialModelException('Duplicated uid "%s"' % emclass.uid)
46
         self.__classes[emclass.uid] = emclass
50
         self.__classes[emclass.uid] = emclass
51
+        return emclass
52
+
53
+    ## @brief Add a group to the editorial model
54
+    # @param emgroup EmGroup : the EmGroup instance to add
55
+    # @return emgroup
56
+    def add_group(self, emgroup):
57
+        if not isinstance(emgroup, EmGroup):
58
+            raise ValueError("<class EmGroup> expected but got %s" % type(emgroup))
59
+        if emgroup.uid in self.groups():
60
+            raise EditorialModelException('Duplicated uid "%s"' % emgroup.uid)
61
+        self.__groups[emgroup.uid] = emgroup
62
+        return emgroup
47
 
63
 
48
     ## @brief Add a new EmClass to the editorial model
64
     ## @brief Add a new EmClass to the editorial model
49
     # @param uid str : EmClass uid
65
     # @param uid str : EmClass uid
50
     # @param **kwargs : EmClass constructor options ( see @ref lodel.editorial_model.component.EmClass.__init__() )
66
     # @param **kwargs : EmClass constructor options ( see @ref lodel.editorial_model.component.EmClass.__init__() )
51
     def new_class(self, uid, **kwargs):
67
     def new_class(self, uid, **kwargs):
52
         return self.add_class(EmClass(uid, **kwargs))
68
         return self.add_class(EmClass(uid, **kwargs))
69
+    
70
+    ## @brief Add a new EmGroup to the editorial model
71
+    # @param uid str : EmGroup uid
72
+    # @param *kwargs : EmGroup constructor keywords arguments (see @ref lodel.editorial_model.component.EmGroup.__init__() )
73
+    def new_group(self, uid, **kwargs):
74
+        return self.add_group(EmGroup(uid, **kwargs))
53
 
75
 
54
     # @brief Save a model
76
     # @brief Save a model
55
     # @param translator module : The translator module to use
77
     # @param translator module : The translator module to use
56
     # @param **translator_args
78
     # @param **translator_args
57
-    def save(self, translator, **translator_args):
58
-        return translator.save(self, **kwargs)
79
+    def save(self, translator, **translator_kwargs):
80
+        return translator.save(self, **translator_kwargs)
59
     
81
     
60
     ## @brief Load a model
82
     ## @brief Load a model
61
     # @param translator module : The translator module to use
83
     # @param translator module : The translator module to use
62
     # @param **translator_args
84
     # @param **translator_args
63
     @classmethod
85
     @classmethod
64
-    def load(cls, translator, **translator_args):
65
-        return translator.load(**kwargs)
86
+    def load(cls, translator, **translator_kwargs):
87
+        return translator.load(**translator_kwargs)
66
     
88
     
67
     ## @brief Private getter for __groups or __classes
89
     ## @brief Private getter for __groups or __classes
68
     # @see classes() groups()
90
     # @see classes() groups()
69
     def __elt_getter(self, elts, uid):
91
     def __elt_getter(self, elts, uid):
70
-        return iter(elts.values) if uid is None else elts[uid]
92
+        return iter(elts.values()) if uid is None else elts[uid]
93
+
94
+    def __hash__(self):
95
+        payload = "%s%s" % (self.name,hash(self.description))
96
+        for guid in sorted(self.__groups):
97
+            payload += str(hash(self.__groups[guid]))
98
+        for cuid in sorted(self.__classes):
99
+            payload += str(hash(self.__classes[cuid]))
100
+        return int.from_bytes(
101
+                                hashlib.md5(bytes(payload, 'utf-8')).digest(),
102
+                                byteorder='big'
103
+        )
71
 
104
 

+ 5
- 4
lodel/editorial_model/translator/picklefile.py View File

1
 #-*- coding: utf-8 -*-
1
 #-*- coding: utf-8 -*-
2
 
2
 
3
 import pickle
3
 import pickle
4
+from pickle import Pickler
4
 
5
 
5
 ## @brief Save a model in a file
6
 ## @brief Save a model in a file
6
 # @param model EditorialModel : the model to save
7
 # @param model EditorialModel : the model to save
7
 # @param filename str|None : if None return the model as pickle bytes
8
 # @param filename str|None : if None return the model as pickle bytes
8
 # @return None if filename is a string, else returns bytes representation of model
9
 # @return None if filename is a string, else returns bytes representation of model
9
-def save(self, model, filename = None):
10
-    with open(filename, 'w+') as ffd:
10
+def save(model, filename = None):
11
+    with open(filename, 'w+b') as ffd:
11
         pickle.dump(model, ffd)
12
         pickle.dump(model, ffd)
12
     return filename
13
     return filename
13
 
14
 
14
 ## @brief Load a model from a file
15
 ## @brief Load a model from a file
15
 # @param filename str : the filename to use to load the model
16
 # @param filename str : the filename to use to load the model
16
-def load(self, filename):
17
-    with open(filename, 'r') as ffd:
17
+def load(filename):
18
+    with open(filename, 'rb') as ffd:
18
         edmod = pickle.load(ffd)
19
         edmod = pickle.load(ffd)
19
     return edmod
20
     return edmod

+ 7
- 5
lodel/utils/mlstring.py View File

1
 #-*- coding: utf-8 -*-
1
 #-*- coding: utf-8 -*-
2
 
2
 
3
+import hashlib
3
 import copy
4
 import copy
4
 import json
5
 import json
5
 
6
 
32
             raise ValueError('<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
33
             raise ValueError('<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
33
     
34
     
34
     ## @brief Return a translation given a lang
35
     ## @brief Return a translation given a lang
35
-    # @param lang str
36
-    def get(self, lang):
36
+    # @param lang str | None : If None return default lang translation
37
+    def get(self, lang = None):
38
+        lang = self.__default_lang if lang is None else lang
37
         if not self.lang_is_valid(lang):
39
         if not self.lang_is_valid(lang):
38
             raise ValueError("Invalid lang : '%s'" % lang)
40
             raise ValueError("Invalid lang : '%s'" % lang)
39
         elif lang in self.values:
41
         elif lang in self.values:
78
         return MlString(json.loads(json_str))
80
         return MlString(json.loads(json_str))
79
 
81
 
80
     def __hash__(self):
82
     def __hash__(self):
81
-        res = ''
83
+        m = hashlib.md5()
82
         for lang in sorted(list(self.values.keys())):
84
         for lang in sorted(list(self.values.keys())):
83
-            res = hash((res, lang, self.values[lang]))
84
-        return res
85
+            m.update(bytes(lang+";"+self.values[lang], 'utf-8'))
86
+        return int.from_bytes(m.digest(), byteorder='big')
85
 
87
 
86
     def __eq__(self, a):
88
     def __eq__(self, a):
87
         return hash(self) == hash(a)
89
         return hash(self) == hash(a)

Loading…
Cancel
Save