root/trac/hacks/marketplugin/0.9/tracmarket/response.py

Revision 111 (checked in by stevegt, 6 years ago)

rework web_ui to be a messaging endpoint

Line 
1 import re
2 import sys
3 import types
4
5 from tracmarket.mq import *
6
7 class Response(dict):
8     '''An hdf-like response object, simpler and more pythonic than
9     hdf, with more of the features we need, such as the ability to
10     return entire subtrees.  Major reason for using this is to provide
11     better isolation between UI modules and command interpreters -- I
12     didn't want the interpreters to know anything about hdf, and the
13     UI will often not be web or clearsilver anyway.  Side benefit is
14     that it lessens dependence on any future decision trac core team
15     might make in regards to use of clearsilver and hdf or HDFWrapper.
16     Provides convenience methods _to_hdf() and _from_hdf() in the
17     meantime.
18
19     >>> res = Response()
20     >>> res.foo.bar = 'baz'
21     >>> res.foo.bar
22     'baz'
23     >>> res.foo
24     {'bar': 'baz'}
25     >>> assert res.foo
26     >>> res.foo.baz.bing
27     {}
28     >>> assert not res.foo.baz.bing
29     >>> res.foo
30     {'bar': 'baz'}
31     >>> assert not res.foo.baz.bing
32     >>> res['foo']
33     {'bar': 'baz'}
34     >>> res['foo']['bar']
35     'baz'
36     >>> res['foo.bar']
37     'baz'
38     >>> res['foo.baz.bing'] = 3
39     >>> res['foo.baz']
40     {'bing': 3}
41     >>> res['foo.baz.bing']
42     3
43     >>> res.foo.baz.bing
44     3
45     >>> assert res.foo.baz.bing
46     >>> assert res.foo.baz.bing == 3
47     >>> res['foo.num.4.blah'] = 'a'
48     >>> res['foo.num.4.blah']
49     'a'
50     >>> res.foo.num[4].blah
51     'a'
52     >>> res.foo['num.4'].blah
53     'a'
54     >>> res['foo.num.5'] = 'b'
55     >>> res.foo.num
56     {'5': 'b', '4': {'blah': 'a'}}
57     >>> res.foo.num[5]
58     'b'
59     >>> res.foo.num[6] = 'c'
60     >>> res.foo.num
61     {'5': 'b', '4': {'blah': 'a'}, '6': 'c'}
62     >>> res.L.a = ('a','b',dict(foo=1,bar=2))
63     >>> res.L.a._type
64     'list'
65     >>> res.L.a
66     ['a', 'b', {'foo': 1, 'bar': 2}]
67     >>> res.L.a[2]
68     {'foo': 1, 'bar': 2}
69     >>> res.L.a[2].foo
70     1
71     >>> res.L.a[1]
72     'b'
73     >>> list(res.L.a)
74     ['a', 'b', {'foo': 1, 'bar': 2}]
75     >>> res.L.a
76     ['a', 'b', {'foo': 1, 'bar': 2}]
77     >>> len(res.L.a)
78     3
79     >>> res.d.a = dict(a=1,b=2,c=3)
80     >>> res.d.a.c
81     3
82     >>> res.foo
83     {'baz': {'bing': 3}, 'num': {'5': 'b', '4': {'blah': 'a'}, '6': 'c'}, 'bar': 'baz'}
84
85     '''
86
87     def __init__(self, bus, path, name=None, val=None, typ='dict'):
88         self._bus = bus
89         self._path = path
90         self._name = name
91         self._type = typ
92         if not val:
93             return
94         if type(val) in (list, tuple, types.GeneratorType):
95             self._type = 'list'
96             for v in val:
97                 self.append(v)
98             return
99         if type(val) in (dict,):
100             for k,v in val.items():
101                 self[k] = v
102             return
103         assert ValueError, "unsupported Response type: %s" % val
104
105     def __iter__(self):
106         keys = []
107         for k,v in super(Response, self).items():
108             if str(k).startswith('_'):
109                 continue
110             # only include Response objects if they're populated
111             if isinstance(v, Response) and not len(v):
112                 continue
113             try:
114                 # convert integer strings to integers
115                 k = int(k)
116                 assert k == float(k)
117             except:
118                 pass
119             keys.append(k)
120         keys.sort()
121         assert self._type in ('dict', 'list')
122         if self._type == 'dict':
123             for k in keys:
124                 yield str(k)
125         if self._type == 'list':
126             for k in keys:
127                 yield self[str(k)]
128
129     def __getattr__(self, name):
130         """allow self.foo.bar"""
131         val = self[name]
132         return val
133
134     def __setattr__(self, name, val):
135         # import pdb; pdb.set_trace()
136         name = str(name)
137         if name.startswith('_'):
138             self.__dict__[name] = val
139         elif name.startswith('__'):
140             super(Response, self).__setattr__(name, val)
141         else:
142             self[name] = val
143
144     def __getitem__(self, name):
145         """allow self['foo.bar']"""
146         name = str(name)
147         item, child = self._parts(name)
148         val = super(Response, self).get(item, None)
149         if val is None:
150             val = self[item] = Response(bus=self._bus, path=self._mkpath(), name=item)
151         if child:
152             return val[child]
153         return val
154
155     def __setitem__(self, name, val):
156         """allow self['foo.bar'] = 'baz'"""
157         if self._type == 'list' and len(self) > 0 and type(name) is int:
158             assert len(self) > int(name)
159         self._set(name, val)
160
161     def _set(self, name, val):
162         name = str(name)
163         # if name == 'order_count':
164         #     import pdb; pdb.set_trace()
165         assert not name.startswith('_'), "invalid res name = " + name
166         item, child = self._parts(name)
167         if type(val) in (list, tuple, dict):
168             val = Response(bus=self._bus, path=self._mkpath(),
169                     name=item, val=val)
170         if child:
171             obj = self[item]
172             obj[child] = val
173         else:
174             msg_data = None
175             if type(val) is Response:
176                 if len(val):
177                     msg_data = val.as_struct()
178             else:
179                 msg_data = val
180             if msg_data is not None:
181                 i=1
182                 sender=None
183                 while True:
184                     try:
185                         frame_name = sys._getframe(i).f_code.co_name
186                         if not frame_name.startswith('_'):
187                             sender = frame_name
188                             break
189                         i += 1
190                     except:
191                         break
192                 path = self._mkpath()
193                 if path:
194                     group_parts = ['res', path, item]
195                 else:
196                     group_parts = ['res', item]
197                 group = '.'.join(group_parts)
198                 # XXX turning off copy because some objects not yet pickleable
199                 msg = Msg(msg_data, sender=sender, cp=False)
200                 self._bus.send(group, msg)
201             super(Response, self).__setitem__(item, val)
202
203     def __len__(self):
204         # only count objects that are populated
205         try:
206             return len(list(self.__iter__()))
207         except:
208             return 0
209
210     def _mkpath(self):
211         if not self._name:
212             return ''
213         if not self._path:
214             return self._name
215         return '.'.join((self._path, self._name))
216
217     def __repr__(self):
218         return repr(self.as_struct())
219
220     def as_struct(self):
221         '''return contents of self as python structure of nested lists
222         and dicts'''
223         assert self._type in ('dict', 'list')
224         if self._type == 'dict':
225             res = {}
226             for k,v in self.items():
227                 if isinstance(v, Response):
228                     if len(v):
229                         res[k] = v.as_struct()
230                 else:
231                     res[k] = v
232         if self._type == 'list':
233             res = []
234             for v in list(self):
235                 if isinstance(v, Response):
236                     if len(v):
237                         res.append(v.as_struct())
238                 else:
239                     res.append(v)
240         return res
241
242     def append(self, val):
243         self._type = 'list'
244         name = len(self)
245         self._set(name, val)
246    
247     def items(self):
248         assert self._type == 'dict'
249         items = []
250         for k in self.__iter__():
251             items.append((k,self[k]))
252         return items
253
254     def info(self, text):
255         self._message('info', text)
256
257     def warning(self, text):
258         self._message('warning', text)
259
260     def error(self, text):
261         self._message('error', text)
262
263     def _message(self, type, text):
264         if not self.market.messages:
265             self.market.messages = []
266         m = dict(text=text, type=type)
267         self.market.messages.append(m)
268
269     def keys(self):
270         assert self._type == 'dict'
271         keys = []
272         for k in self.__iter__():
273             keys.append(k)
274         return keys
275
276     def _parts(self, name):
277         names = str(name).split('.')
278         item = names[0]
279         if len(names) > 1:
280             child = '.'.join(names[1:])
281         else:
282             child = None
283         return item, child
284
285     def dump(self, prefix=None):
286         '''return a string dump in "foo.bar.baz = 'bing'\n" format'''
287         struct = self.as_struct()
288         out = ''
289         for name, val in self._walk_struct(struct):
290             out += "%s = %s\n" % (name, val)
291         return out
292
293     def _walk_struct(self, struct, prefix=None, recurse=True):
294         index = None
295         if issubclass(struct.__class__, (list, tuple)):
296             index = range(len(struct))
297         elif issubclass(struct.__class__, dict):
298             index = struct.keys()
299         if index:
300             for i in index:
301                 try:
302                     val = struct[i]
303                 except TypeError:
304                     raise ValueError, \
305                         "expected index, got %s, dir = %s" % (type(i), dir(i))
306                 if prefix:
307                     name = prefix + "." + str(i)
308                 else:
309                     name = str(i)
310                 if recurse:
311                     for name, val in self._walk_struct(val, prefix=name):
312                         yield name, val
313                 else:
314                     yield name, val
315         else:
316             yield prefix, struct
317
318     def to_hdf(self, hdf, prefix=None):
319         '''populate hdf from self'''
320         # XXX refactor to use _walk_struct(..., recurse=False)
321         if not len(self):
322             return
323         struct = self.as_struct()
324         # only iterate over self's immediate children; set_value()
325         # will recurse for us
326         if self._type == 'list':
327             for i in range(len(self)):
328                 val = struct[i]
329                 if prefix:
330                     name = prefix + "." + i
331                 else:
332                     name = i
333                 hdf.set_value(name, val)
334         if self._type == 'dict':
335             for k in self.keys():
336                 val = struct[k]
337                 if prefix:
338                     name = prefix + "." + k
339                 else:
340                     name = k
341                 hdf.set_value(name, val)
342
343     def from_hdf(cls, hdf):
344         def hdf_tree_walk(node, name=None):
345             res = Response(bus=self._bus, path=self._mkpath(), name=name)
346             while node:
347                 var = node.name()
348                 val = node.value()
349                 # XXX can't handle nodes with both value and children
350                 if node.child():
351                     child = hdf_tree_walk(node.child(), var)
352                     res[var] = child
353                 else:
354                     res[var] = val
355                 node = node.next()
356             return res
357         # import pdb; pdb.set_trace()
358         res = hdf_tree_walk(hdf.child())
359         return res
360     from_hdf = classmethod(from_hdf)
361
Note: See TracBrowser for help on using the browser.