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,6 +3,7 @@
3 3
 import itertools
4 4
 import warnings
5 5
 import copy
6
+import hashlib
6 7
 
7 8
 from lodel.utils.mlstring import MlString
8 9
 
@@ -29,6 +30,18 @@ class EmComponent(object):
29 30
             return str(self.uid)
30 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 45
 ## @brief Handles editorial model objects classes
33 46
 #
34 47
 # @note The inheritance system allow child classes to overwrite parents EmField. But it's maybe not a good idea
@@ -84,6 +97,7 @@ class EmClass(EmComponent):
84 97
             raise EditorialModelException("Duplicated uid '%s' for EmField in this class ( %s )" % (emfield.uid, self))
85 98
         self.__fields[emfield.uid] = emfield
86 99
         emfield._emclass = self
100
+        return emfield
87 101
     
88 102
     ## @brief Create a new EmField and add it to the EmClass
89 103
     # @param uid str : the EmField uniq id
@@ -91,6 +105,14 @@ class EmClass(EmComponent):
91 105
     def new_field(self, uid, **field_kwargs):
92 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 117
 ## @brief Handles editorial model classes fields
96 118
 class EmField(EmComponent):
@@ -109,6 +131,16 @@ class EmField(EmComponent):
109 131
         ## @brief Stores the emclass that contains this field (set by EmClass.add_field() method)
110 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 145
 ## @brief Handles functionnal group of EmComponents
114 146
 class EmGroup(object):
@@ -184,9 +216,29 @@ class EmGroup(object):
184 216
     def __circular_dependencie(self, new_dep):
185 217
         return self.uid in new_dep.dependencies(True)
186 218
 
219
+    ## @brief Fancy string representation of an EmGroup
220
+    # @return a string
187 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 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,8 +1,11 @@
1 1
 #-*- coding:utf-8 -*-
2
+
3
+import hashlib
4
+
2 5
 from lodel.utils.mlstring import MlString
3 6
 
4 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 10
 ## @brief Describe an editorial model
8 11
 class EditorialModel(object):
@@ -23,7 +26,7 @@ class EditorialModel(object):
23 26
     # @return if uid given return an EmClass else return an EmClass iterator
24 27
     def classes(self, uid = None):
25 28
         try:
26
-            return __elt_getter(self.__classes)
29
+            return self.__elt_getter(self.__classes, uid)
27 30
         except KeyError:
28 31
             raise EditorialModelException("EmClass not found : '%s'" % uid)
29 32
 
@@ -32,40 +35,70 @@ class EditorialModel(object):
32 35
     # @return if uid given return an EmGroup else return an EmGroup iterator
33 36
     def groups(self, uid = None):
34 37
         try:
35
-            return __elt_getter(self.__groups)
38
+            return self.__elt_getter(self.__groups, uid)
36 39
         except KeyError:
37 40
             raise EditorialModelException("EmGroup not found : '%s'" % uid)
38 41
 
39 42
     ## @brief Add a class to the editorial model
40 43
     # @param emclass EmClass : the EmClass instance to add
44
+    # @return emclass
41 45
     def add_class(self, emclass):
42 46
         if not isinstance(emclass, EmClass):
43 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 49
             raise EditorialModelException('Duplicated uid "%s"' % emclass.uid)
46 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 64
     ## @brief Add a new EmClass to the editorial model
49 65
     # @param uid str : EmClass uid
50 66
     # @param **kwargs : EmClass constructor options ( see @ref lodel.editorial_model.component.EmClass.__init__() )
51 67
     def new_class(self, uid, **kwargs):
52 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 76
     # @brief Save a model
55 77
     # @param translator module : The translator module to use
56 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 82
     ## @brief Load a model
61 83
     # @param translator module : The translator module to use
62 84
     # @param **translator_args
63 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 89
     ## @brief Private getter for __groups or __classes
68 90
     # @see classes() groups()
69 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,19 +1,20 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3 3
 import pickle
4
+from pickle import Pickler
4 5
 
5 6
 ## @brief Save a model in a file
6 7
 # @param model EditorialModel : the model to save
7 8
 # @param filename str|None : if None return the model as pickle bytes
8 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 12
         pickle.dump(model, ffd)
12 13
     return filename
13 14
 
14 15
 ## @brief Load a model from a file
15 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 19
         edmod = pickle.load(ffd)
19 20
     return edmod

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

@@ -1,5 +1,6 @@
1 1
 #-*- coding: utf-8 -*-
2 2
 
3
+import hashlib
3 4
 import copy
4 5
 import json
5 6
 
@@ -32,8 +33,9 @@ class MlString(object):
32 33
             raise ValueError('<class str>, <class dict> or <class MlString> expected, but %s found' % type(arg))
33 34
     
34 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 39
         if not self.lang_is_valid(lang):
38 40
             raise ValueError("Invalid lang : '%s'" % lang)
39 41
         elif lang in self.values:
@@ -78,10 +80,10 @@ class MlString(object):
78 80
         return MlString(json.loads(json_str))
79 81
 
80 82
     def __hash__(self):
81
-        res = ''
83
+        m = hashlib.md5()
82 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 88
     def __eq__(self, a):
87 89
         return hash(self) == hash(a)

Loading…
Cancel
Save