1 """SpecMessage module
2
3 This module defines classes and functions for creating messages
4 from data received from Spec, and for generating messages to be
5 sent to Spec.
6
7 It handles the different message versions (headers 2, 3 and 4).
8 """
9
10 __author__ = 'Matias Guijarro'
11 __version__ = '1.0'
12
13 import struct
14 import time
15 import types
16
17 import SpecArray
18 import SpecReply
19
20 (DOUBLE, STRING, ERROR, ASSOC) = (1,2,3,4)
21
22 MAGIC_NUMBER=4277009102
23 NATIVE_HEADER_VERSION=4
24 NULL='\000'
25
26
27 (CLOSE, ABORT, CMD, CMD_WITH_RETURN, REGISTER, UNREGISTER, EVENT, \
28 FUNC, FUNC_WITH_RETURN, CHAN_READ, CHAN_SEND, REPLY, HELLO, HELLO_REPLY) = \
29 (1,2,3,4,6,7,8,9,10,11,12,13,14,15)
30
31
32 DELETED = 0x0001
33
34
36 """Return a new SpecMessage object
37
38 The returned SpecMessage object can be of any of the available message
39 class. You can specify the desired message class with the 'version' keyword
40 argument. If not specified, defaults to NATIVE_HEADER_VERSION.
41
42 Arguments are passed to the appropriate message constructor.
43
44 Keyword arguments:
45 version -- message version, defaults to NATIVE_HEADER_VERSION. When
46 reading messages from stream, you can set it to None and it will try
47 to guess the suitable message class by reading the header version
48 from Spec.
49 """
50 version = kwargs.get('version', NATIVE_HEADER_VERSION)
51 order = kwargs.get('order', '<')
52 if len(order) == 0:
53 order = "<"
54
55 if version == 4:
56 m = message4(*args)
57 elif version == 3:
58 m = message3(*args)
59 elif version == 2:
60 m = message2(*args)
61 else:
62 m = anymessage(*args)
63
64 m.packedHeaderDataFormat=order+m.packedHeaderDataFormat[1:]
65
66 return m
67
68
70 """Transform a list as coming from a SPEC associative array
71 to a dictonary - 2dim arrays are transformed top dict with dict
72 entries. In SPEC the key contains \x1c"""
73 raw = rawstring.split(NULL)[:-2]
74
75 data = {}
76 for i in range(0,len(raw) - 1,2):
77 key,val = raw[i], raw[i+1]
78 keyel = key.split("\x1c")
79 if len(keyel) == 1:
80 if key in data:
81 data[key][None] = val
82 else:
83 data[key]=val
84 else:
85 if keyel[0] in data and type(data[keyel[0]])!=types.DictType:
86 data[keyel[0]]={ None: data[keyel[0]] }
87
88 try:
89 data[keyel[0]][keyel[1]] = val
90 except TypeError:
91 data[keyel[0]] = {keyel[1] : val}
92 except KeyError:
93 data[keyel[0]] = {keyel[1] : val}
94 return data
95
96
98 """Transform a Python dictionary object to the string format
99 expected by Spec"""
100 data = ""
101 for key, val in dict.items():
102 if type(val) == types.DictType:
103 for kkey, vval in val.iteritems():
104 if kkey is None:
105 data += str(key) + NULL + str(vval) + NULL
106 else:
107 data += ''.join([str(key), '\x1c', str(kkey), NULL, str(vval), NULL])
108 else:
109 data += str(key) + NULL + str(val) + NULL
110
111 return (len(data) > 0 and data) or NULL
112
113
115 """Base class for messages."""
117 """Constructor
118
119 Arguments:
120 packedHeader -- string representing the packed header format for the message,
121 use the same syntax as the 'struct' Python module
122 """
123 self.packedHeaderDataFormat = packedHeader
124 self.headerLength = struct.calcsize(self.packedHeaderDataFormat)
125 self.bytesToRead = self.headerLength
126 self.readheader = True
127 self.data = ''
128 self.type = None
129
130
131 self.magic = None
132 self.vers = None
133 self.size = None
134 self.sn = None
135 self.sec = None
136 self.usec = None
137 self.cmd = None
138 self.type = None
139 self.rows = 0
140 self.cols = 0
141 self.name = None
142 self.err = 0
143 self.flags = 0
144
145
147 """Return wether a message read from stream has been fully received or not."""
148 return self.bytesToRead == 0
149
150
152 """Read buffer from stream and try to create a message from it
153
154 Arguments:
155 streamBuf - string buffer of the last bytes received from Spec
156
157 Return value :
158 the number of consumed bytes
159 """
160 consumedBytes = 0
161
162 while self.bytesToRead > 0 and len(streamBuf[consumedBytes:]) >= self.bytesToRead:
163 if self.readheader:
164 self.readheader = False
165 self.type, self.bytesToRead = self.readHeader(streamBuf[:self.headerLength])
166 consumedBytes = self.headerLength
167 else:
168 rawdata = streamBuf[consumedBytes:consumedBytes+self.bytesToRead]
169 consumedBytes += self.bytesToRead
170 self.bytesToRead = 0
171
172 self.data = self.readData(rawdata, self.type)
173
174 return consumedBytes
175
176
178 """Read the header of the message coming from stream
179
180 Arguments:
181 rawstring -- raw bytes of the header
182
183 Return value:
184 (message data type, message data len) tuple
185 """
186 return (None, 0)
187
188
189 - def readData(self, rawstring, datatype):
190 """Read the data part of the message coming from stream
191
192 Arguments:
193 rawstring -- raw data bytes
194 datatype -- data type
195
196 Return value:
197 the data read
198 """
199 data = rawstring[:-1]
200
201 if datatype == ERROR:
202 return data
203 elif datatype == STRING or datatype == DOUBLE:
204
205 try:
206 data = int(data)
207 except:
208 try:
209 data = float(data)
210 except:
211 pass
212
213 return data
214 elif datatype == ASSOC:
215 return rawtodictonary(rawstring)
216 elif SpecArray.isArrayType(datatype):
217
218
219 return SpecArray.SpecArray(rawstring, datatype, self.rows, self.cols)
220 else:
221 raise TypeError
222
223
225 """Try to guess data type
226
227 Works for obvious cases only
228 - it is a hard job guessing ARRAY_* types, we ignore this case (user has to provide a suitable datatype)
229 - we cannot make a difference between ERROR type and STRING type
230 """
231 if type(data) == types.StringType:
232 return STRING
233 elif type(data) == types.DictType:
234 return ASSOC
235 elif type(data) == types.IntType or type(data) == types.LongType or type(data) == types.FloatType:
236 return STRING
237
238 elif isinstance(data, SpecArray.SpecArrayData):
239 self.rows, self.cols = data.shape
240 return data.type
241
242
244 """Return the string representing the data part of the message."""
245 rawstring = ''
246
247 if datatype in (ERROR, STRING, DOUBLE):
248 rawstring = str(data)
249 elif datatype == ASSOC:
250 rawstring = dictionarytoraw(data)
251 elif SpecArray.isArrayType(datatype):
252 rawstring = data.tostring()
253
254 if len(rawstring) > 0:
255 rawstring += NULL
256
257 return rawstring
258
259
261 """Create a string representing the message which can be send
262 over the socket."""
263 return ''
264
265
267 """Version 2 message class"""
269 """Constructor
270
271 If called without arguments, message is supposed to be read from stream.
272 Otherwise, the 'init' method is called with the specified arguments, for
273 creating a message from arguments.
274 """
275 SpecMessage.__init__(self, '<IiiiIIiiIII80s')
276
277 if len(args) > 0:
278 self.init(*args, **kwargs)
279
280
281 - def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0):
282 """ Create a message from the arguments"""
283 self.vers = 2
284 self.size = self.headerLength
285 self.magic = MAGIC_NUMBER
286 self.rows = rows
287 self.cols = cols
288 self.data = data
289 self.type = datatype or self.dataType(self.data)
290 self.time = time.time()
291 self.sec = int(self.time)
292 self.usec = int((self.time-self.sec)*1E6)
293 self.sn, self.cmd, self.name = ser, cmd, str(name)
294
295
297 self.magic, self.vers, self.size, self.sn, \
298 self.sec, self.usec, self.cmd, \
299 datatype, self.rows, self.cols, \
300 datalen, name = struct.unpack(self.packedHeaderDataFormat, rawstring)
301 if self.magic != MAGIC_NUMBER:
302 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:]
303 self.magic, self.vers, self.size, self.sn, \
304 self.sec, self.usec, self.cmd, \
305 datatype, self.rows, self.cols, \
306 datalen, name = struct.unpack(self.packedHeaderDataFormat, rawstring)
307
308 self.time = self.sec + float(self.usec) / 1E6
309 self.name = name.replace(NULL, '')
310
311 return (datatype, datalen)
312
313
315 if self.type is None:
316
317 return ''
318
319 data = self.sendingDataString(self.data, self.type)
320 datalen = len(data)
321
322 header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size,
323 self.sn, self.sec, self.usec, self.cmd, self.type,
324 self.rows, self.cols, datalen, str(self.name))
325
326
327 return header + data
328
329
336
337
338 - def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0):
339 """ Create a message from the arguments """
340 self.vers = 3
341 self.size = self.headerLength
342 self.magic = MAGIC_NUMBER
343 self.rows = rows
344 self.cols = cols
345 self.data = data
346 self.type = datatype or self.dataType(self.data)
347 self.time = time.time()
348 self.sec = int(self.time)
349 self.usec = int((self.time-self.sec)*1E6)
350 self.sn, self.cmd, self.name = ser, cmd, str(name)
351
352
354 self.magic, self.vers, self.size, self.sn, \
355 self.sec, self.usec, self.cmd, \
356 datatype, self.rows, self.cols, \
357 datalen, self.err, name = struct.unpack(self.packedHeaderDataFormat, rawstring)
358 if self.magic != MAGIC_NUMBER:
359 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:]
360 self.magic, self.vers, self.size, self.sn, \
361 self.sec, self.usec, self.cmd, \
362 datatype, self.rows, self.cols, \
363 datalen, self.err, name = struct.unpack(self.packedHeaderDataFormat, rawstring)
364
365 self.time = self.sec + float(self.usec) / 1E6
366 self.name = name.replace(NULL, '')
367
368 if self.err > 0:
369 datatype = ERROR
370
371 return (datatype, datalen)
372
373
375 if self.type is None:
376
377 return ''
378
379 data = self.sendingDataString(self.data, self.type)
380 datalen = len(data)
381
382 header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size,
383 self.sn, self.sec, self.usec, self.cmd, self.type,
384 self.rows, self.cols, datalen, self.err, str(self.name))
385
386
387
388 return header + data
389
390
397
398
399 - def init(self, ser, cmd, name, data, datatype = None, rows = 0, cols = 0):
400 """ Create a message from the arguments """
401 self.vers = 4
402 self.size = self.headerLength
403 self.magic = MAGIC_NUMBER
404 self.rows = rows
405 self.cols = cols
406 self.data = data
407 self.type = datatype or self.dataType(self.data)
408 self.time = time.time()
409 self.sec = int(self.time)
410 self.usec = int((self.time-self.sec)*1E6)
411 self.sn, self.cmd, self.name = ser, cmd, str(name)
412
413
415 self.magic, self.vers, self.size, self.sn, \
416 self.sec, self.usec, self.cmd, \
417 datatype, self.rows, self.cols, \
418 datalen, self.err, self.flags, name = struct.unpack(self.packedHeaderDataFormat, rawstring)
419 if self.magic != MAGIC_NUMBER:
420 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:]
421 self.magic, self.vers, self.size, self.sn, \
422 self.sec, self.usec, self.cmd, \
423 datatype, self.rows, self.cols, \
424 datalen, self.err, self.flags, name = struct.unpack(self.packedHeaderDataFormat, rawstring)
425
426 self.time = self.sec + float(self.usec) / 1E6
427 self.name = name.replace(NULL, '')
428
429 if self.err > 0:
430 datatype = ERROR
431
432 return (datatype, datalen)
433
434
436 if self.type is None:
437
438 return ''
439
440 data = self.sendingDataString(self.data, self.type)
441 datalen = len(data)
442
443
444
445 header = struct.pack(self.packedHeaderDataFormat, self.magic, self.vers, self.size,
446 self.sn, self.sec, self.usec, self.cmd, self.type,
447 self.rows, self.cols, datalen, self.err, self.flags, str(self.name))
448
449 return header + data
450
451
455
456
458 if len(streamBuf) >= self.bytesToRead:
459 magic, version = struct.unpack(self.packedHeaderDataFormat, streamBuf[:self.headerLength])
460
461 if magic != MAGIC_NUMBER:
462 self.packedHeaderDataFormat=">"+self.packedHeaderDataFormat[1:]
463 magic, version = struct.unpack(self.packedHeaderDataFormat, streamBuf[:self.headerLength])
464
465
466 if version == 2:
467 self.__class__ = message2
468 message2.__init__(self)
469 return self.readFromStream(streamBuf)
470 elif version == 3:
471 self.__class__ = message3
472 message3.__init__(self)
473 return self.readFromStream(streamBuf)
474 elif version >= 4:
475 self.__class__ = message4
476 message4.__init__(self)
477 return self.readFromStream(streamBuf)
478
479 return 0
480
481
483 """Convert a command list to a Spec command string."""
484 if type(cmdlist) == types.ListType and len(cmdlist) > 0:
485 cmd = [str(cmdlist[0])]
486
487 for arg in cmdlist[1:]:
488 argstr = repr(arg)
489
490 if type(arg) == types.DictType:
491 argstr = argstr.replace('{', '[')
492 argstr = argstr.replace('}', ']')
493
494 cmd.append(argstr)
495
496 return NULL.join(cmd)
497 else:
498 return ''
499
500
504
505
510
511
513 """Return a command without reply message"""
514 return message_no_reply(CMD, "", cmd, version, order)
515
516
521
522
526
527
531
532
536
537
541
542
546
547
551
552
556
557
561
562
564 return message(replyID, HELLO_REPLY, serverName, serverName, version = version, order=order)
565
566
567
569 """ Lower level call to send a message of a certain type """
570 newReply = SpecReply.SpecReply()
571 replyID = newReply.id
572
573 m = message(replyID, cmd, name, data, version = version, order=order)
574
575 return (newReply, m)
576
577
579 """ Send a message which will not result in a reply from the server.
580 If a reply is sent depends only on the cmd and not on the method
581 to send the message """
582 return message(0, cmd, name, data, version = version, order=order)
583
584
586 return message(replyID, REPLY, name, data, version = version, order=order)
587
588
590 return message(replyID, REPLY, name, data, ERROR, version = version, order=order)
591