~unifield-team/unifield-web/unifield-merged-openerp-client-web

« back to all changes in this revision

Viewing changes to addons/openerp/utils/utils.py

  • Committer: jf
  • Date: 2012-07-03 14:35:06 UTC
  • Revision ID: jf@tempo4-20120703143506-ejlqlqdxsia1xz4f
OEB-123 TinyDict and concurrent access
lp:openobject-client-web at revision 4828

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
#  You can see the MPL licence at: http://www.mozilla.org/MPL/MPL-1.1.html
19
19
#
20
20
###############################################################################
 
21
from __future__ import with_statement
21
22
import itertools
22
23
import re
23
24
 
25
26
import formencode
26
27
import openobject
27
28
 
28
 
 
29
 
def _make_dict(data, is_params=False):
30
 
    """If is_params is True then generates a TinyDict otherwise generates a valid
31
 
    dictionary from the given data to be used with OpenERP.
32
 
 
33
 
    @param data: data in the form of {'a': 1, 'b/x': 1, 'b/y': 2}
34
 
    @param is_params: if True generate TinyDict instead of standard dict
35
 
 
36
 
    @return: TinyDict or dict
 
29
crummy_pseudoliteral_matcher = re.compile('^(True|False|None|-?\d+(\.\d+)?|\[.*?\]|\(.*?\)|\{.*?\})$', re.M)
 
30
 
 
31
class noeval(object):
 
32
    """contextmanager that prevent TinyDict from doing evals"""
 
33
    def __init__(self, d):
 
34
        self.d = d
 
35
    def __enter__(self):
 
36
        self.d.set_config_noeval(True)
 
37
        return self.d
 
38
    def __exit__(self, exc_type, exc_val, exc_tb):
 
39
        self.d.set_config_noeval(False)
 
40
        return False # propage exception if any
 
41
 
 
42
class TinyDict(dict):
 
43
    """A dictionary class that allows accessing it's items as it's attributes.
 
44
    It also converts stringified Boolean, None, Number or secuence to python object.
 
45
    This class is mainly used by Controllers to get special `_terp_` arguments and
 
46
    to generate valid dictionary of data fields from the controller keyword arguments.
37
47
    """
38
 
    
39
 
    def make_dict_internal(data, is_params=False, previous_dict_ids=None):
 
48
    __config_noeval = False
 
49
 
 
50
    def __init__(self, **kwargs):
 
51
        super(TinyDict, self).__init__()
 
52
        for k, v in kwargs.items():
 
53
            if (isinstance(v, dict) and not isinstance(v, TinyDict)):
 
54
                v = TinyDict(**v)
 
55
            self[k] = v
 
56
 
 
57
    def get_config_noeval(self):
 
58
        return self._TinyDict__config_noeval
 
59
 
 
60
    def set_config_noeval(self, value):
 
61
        dict.__setattr__(self, '_TinyDict__config_noeval', value)
 
62
        # recursively propagate the new value
 
63
        for v in self.itervalues():
 
64
            if isinstance(v, TinyDict):
 
65
                v.set_config_noeval(value)
 
66
 
 
67
    def build_new(self, is_params=True):
 
68
        if not is_params:
 
69
            return {}
 
70
        new_dict = type(self)()
 
71
        # propagate noeval config flag
 
72
        new_dict.set_config_noeval(self.get_config_noeval())
 
73
        return new_dict
 
74
 
 
75
    def build_dict(self, data, is_params=False, previous_dict_ids=None):
 
76
        """If is_params is True then generates a TinyDict otherwise generates a valid
 
77
        dictionary from the given data to be used with OpenERP.
 
78
 
 
79
        @param data: data in the form of {'a': 1, 'b/x': 1, 'b/y': 2}
 
80
        @param is_params: if True generate TinyDict instead of standard dict
 
81
        @param previous_dict_ids: set of dict instance to avoid recursion
 
82
 
 
83
        @return: TinyDict or dict
 
84
        """
 
85
        if previous_dict_ids is None:
 
86
            previous_dict_ids = set()
 
87
 
40
88
        if id(data) in previous_dict_ids:
41
 
            raise ValueError("Recursive dictionary detected, _make_dict does not handle recursive dictionaries.")
 
89
            raise ValueError("Recursive dictionary detected, build_dict does not handle recursive dictionaries.")
42
90
        previous_dict_ids.add(id(data))
43
91
    
44
 
        res = (is_params or {}) and TinyDict()
 
92
        res = self.build_new(is_params)
45
93
    
46
94
        for name, value in data.items():
47
95
    
54
102
                root = names[0]
55
103
                if root in res and not isinstance(res[root], dict):
56
104
                    del res[root]
57
 
                res.setdefault(root, (is_params or {}) and TinyDict()).update({"/".join(names[1:]): value})
 
105
                res.setdefault(root, self.build_new(is_params)).update({"/".join(names[1:]): value})
58
106
            elif name not in res:
59
107
                # if name is already in res, it might be an o2m value
60
108
                # which tries to overwrite a recursive object/dict
66
114
                    _id = v.pop('__id') or 0
67
115
                    _id = int(_id)
68
116
    
69
 
                    values = _make_dict(v, is_params)
 
117
                    # build new dict without keeping previous_dict_ids references
 
118
                    values = self.build_dict(v, is_params, previous_dict_ids=None)
70
119
                    if values and any(values.itervalues()):
71
120
                        res[k] = [(_id and 1, _id, values)]
72
121
                    else:
73
122
                        res[k] = []
74
123
    
75
124
                else:
76
 
                    res[k] = make_dict_internal(v, is_params and isinstance(v, TinyDict), previous_dict_ids)
 
125
                    res[k] = self.build_dict(v, is_params and isinstance(v, TinyDict), previous_dict_ids)
77
126
    
78
127
        previous_dict_ids.remove(id(data))
79
128
        return res
80
 
    
81
 
    return make_dict_internal(data, is_params, set())
82
 
 
83
 
 
84
 
crummy_pseudoliteral_matcher = re.compile('^(True|False|None|-?\d+(\.\d+)?|\[.*?\]|\(.*?\)|\{.*?\})$', re.M)
85
 
class TinyDict(dict):
86
 
    """A dictionary class that allows accessing it's items as it's attributes.
87
 
    It also converts stringified Boolean, None, Number or secuence to python object.
88
 
    This class is mainly used by Controllers to get special `_terp_` arguments and
89
 
    to generate valid dictionary of data fields from the controller keyword arguments.
90
 
    """
91
 
 
92
 
    def __init__(self, **kwargs):
93
 
        super(TinyDict, self).__init__()
94
 
 
95
 
        for k, v in kwargs.items():
96
 
            if (isinstance(v, dict) and not isinstance(v, TinyDict)):
97
 
                v = TinyDict(**v)
98
 
            self[k] = v
99
129
 
100
130
    def _eval(self, value):
 
131
        if self.get_config_noeval():
 
132
            # current behaviour is to store raw value without evaluating them
 
133
            return value
101
134
 
102
135
        if isinstance(value, list):
103
136
            for i, v in enumerate(value):
122
155
 
123
156
        return value
124
157
 
 
158
    _real_eval = _eval # keep a reference of real _eval method
 
159
 
125
160
    def __setattr__(self, name, value):
126
161
        name = '_terp_%s' % name
127
162
        value = self._eval(value)
165
200
 
166
201
        return value
167
202
 
168
 
    @staticmethod
169
 
    def split(kwargs):
 
203
    def split_dict(self, kwargs):
170
204
        """A helper function to extract special parameters from the given kwargs.
171
205
 
172
206
        @param kwargs: dict of keyword arguments
174
208
        @rtype: tuple
175
209
        @return: tuple of dicts, (TinyDict, dict of data)
176
210
        """
177
 
 
178
 
        params = TinyDict()
 
211
        params = self.build_new()
179
212
        data = {}
180
213
 
181
214
        for n, v in kwargs.items():
184
217
            else:
185
218
                data[n] = v
186
219
 
187
 
        return _make_dict(params, True), _make_dict(data, False)
 
220
        return self.build_dict(params, True), self.build_dict(data, False)
 
221
 
 
222
    @staticmethod
 
223
    def split(kwargs):
 
224
        return TinyDict().split_dict(kwargs)
188
225
 
189
226
    def make_plain(self, prefix=''):
190
227
 
320
357
                    raise TinyFormError(name.replace('_terp_form/', ''), e.msg, e.value)
321
358
 
322
359
        # Prevent auto conversion from TinyDict
323
 
        _eval = TinyDict._eval
324
 
        TinyDict._eval = lambda self, v: v
325
 
 
326
 
        try:
327
 
            params, data = TinyDict.split(kw)
 
360
        with noeval(TinyDict()) as converted_dict:
 
361
            params, data = converted_dict.split_dict(kw)
328
362
            params = params.form or {}
329
 
 
330
 
            return TinyDict(**params)
331
 
 
332
 
        finally:
333
 
            TinyDict._eval = _eval
 
363
            converted_dict.update(**params)
 
364
            return converted_dict
334
365
 
335
366
    def from_python(self):
336
367
        return self._convert(False)